NET MAUI - XAML flyout navigation

2022. 7. 20. 00:00MAUI

반응형

플라이아웃 탐색은 메뉴 항목 창이 장치 화면의 측면에서 미끄러지듯 나타나는 탐색 유형이다.

일반적으로 "햄버거" 메뉴 또는 세 개의 수평선이 서로 겹쳐진 아이콘을 탭하면 호출된다.

 

플라이아웃 메뉴는 Header, FlyoutItems, MenuItems 및 바닥글의 여러 부분으로 구성된다.

 

 

기본 페이지 역할을 하는 AppShell 에  호스팅 되어야 한다. 

<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
       xmlns:controls="clr-namespace:Xaminals.Controls"
       xmlns:views="clr-namespace:Xaminals.Views"
       x:Class="Xaminals.AppShell">
    <FlyoutItem Title="Cats"
                Icon="cat.png">
       <Tab>
           <ShellContent ContentTemplate="{DataTemplate views:CatsPage}" />
       </Tab>
    </FlyoutItem>
    <FlyoutItem Title="Dogs"
                Icon="dog.png">
       <Tab>
           <ShellContent ContentTemplate="{DataTemplate views:DogsPage}" />
       </Tab>
    </FlyoutItem>
</Shell>

예제)

Xaml\FlyoutNavigation 을 기본 위치로 하여 작성

Pages\MainPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MauiApp1.Xaml.FlyoutNavigation.Pages.MainPage"
             Title="MainPage"
             BackgroundColor="Black">
    <ContentPage.Resources>
        <ResourceDictionary>
            <Color x:Key="textColor">White</Color>
            <Color x:Key="boxColor">#44FFFFFF</Color>
            <Style TargetType="Label">
                <Setter Property="TextColor" Value="{StaticResource textColor}" />
                <Setter Property="HorizontalOptions" Value="Center" />
            </Style>
        </ResourceDictionary>
    </ContentPage.Resources>

    <Grid RowSpacing="10" ColumnSpacing="5" Padding="5" >
        <Grid.RowDefinitions>
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="auto" />
            <ColumnDefinition Width="auto" />
            <ColumnDefinition Width="auto" />
            <ColumnDefinition Width="auto" />
        </Grid.ColumnDefinitions>

        <Image Source="starfield.png" Aspect="AspectFill" Grid.RowSpan="4" Grid.ColumnSpan="4" />

        <StackLayout Grid.Row="0" Grid.ColumnSpan="4">
            <Label x:Name="lblDate" FontAttributes="Bold" />
            <Label x:Name="lblMoonPhaseIcon" FontSize="96" />
            <Label x:Name="lblMoonPhaseText" FontAttributes="Bold" />
        </StackLayout>

        <Label x:Name="lblPhaseIcon1" Grid.Column="0" Grid.Row="1" FontSize="48" />
        <Label x:Name="lblPhaseIcon2" Grid.Column="1" Grid.Row="1" FontSize="48" />
        <Label x:Name="lblPhaseIcon3" Grid.Column="2" Grid.Row="1" FontSize="48" />
        <Label x:Name="lblPhaseIcon4" Grid.Column="3" Grid.Row="1" FontSize="48" />

        <Label x:Name="lblPhaseText1" Grid.Column="0" Grid.Row="2" FontAttributes="Bold" FontSize="Medium" />
        <Label x:Name="lblPhaseText2" Grid.Column="1" Grid.Row="2" FontAttributes="Bold" FontSize="Medium" />
        <Label x:Name="lblPhaseText3" Grid.Column="2" Grid.Row="2" FontAttributes="Bold" FontSize="Medium" />
        <Label x:Name="lblPhaseText4" Grid.Column="3" Grid.Row="2" FontAttributes="Bold" FontSize="Medium" />

    </Grid>
</ContentPage>

Pages\MainPage.xaml.cs

using MauiApp1.Xaml.FlyoutNavigation.Data;

namespace MauiApp1.Xaml.FlyoutNavigation.Pages;

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();
        InitializeUI();
    }
    void InitializeUI()
    {
        var phase = MoonPhaseCalculator.GetPhase(DateTime.Now);

        lblDate.Text = DateTime.Today.ToString("D");
        lblMoonPhaseIcon.Text = moonPhaseEmojis[phase];
        lblMoonPhaseText.Text = phase.ToString();

        SetMoonPhaseLabels(lblPhaseIcon1, lblPhaseText1, 1);
        SetMoonPhaseLabels(lblPhaseIcon2, lblPhaseText2, 2);
        SetMoonPhaseLabels(lblPhaseIcon3, lblPhaseText3, 3);
        SetMoonPhaseLabels(lblPhaseIcon4, lblPhaseText4, 4);
    }

    void SetMoonPhaseLabels(Label lblIcon, Label lblText, int dayOffset)
    {
        var phase = MoonPhaseCalculator.GetPhase(DateTime.Now.AddDays(dayOffset));
        lblIcon.Text = moonPhaseEmojis[phase];
        lblText.Text = DateTime.Now.AddDays(dayOffset).DayOfWeek.ToString();
    }

    static Dictionary<MoonPhaseCalculator.Phase, string> moonPhaseEmojis = new Dictionary<MoonPhaseCalculator.Phase, string>
        {
            { MoonPhaseCalculator.Phase.New, "🌑" },
            { MoonPhaseCalculator.Phase.WaxingCrescent, "🌒" },
            { MoonPhaseCalculator.Phase.FirstQuarter, "🌓" },
            { MoonPhaseCalculator.Phase.WaxingGibbous, "🌔" },
            { MoonPhaseCalculator.Phase.Full, "🌕" },
            { MoonPhaseCalculator.Phase.WaningGibbous, "🌖" },
            { MoonPhaseCalculator.Phase.LastQuarter, "🌗" },
            { MoonPhaseCalculator.Phase.WaningCrescent, "🌘" },
        };
}

Pages\AboutPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MauiApp1.Xaml.FlyoutNavigation.Pages.AboutPage"
             Title="AboutPage">
    <StackLayout Padding="10" Spacing="5">
        <Label Text="Version 1.0.0" />
        <Label Text="Sunrise/Sunset data provided by: https://sunrise-sunset.org/api" />
        <Label Text="Icons from Font Awesome" />
    </StackLayout>
</ContentPage>

Pages\AboutPage.xaml.cs

namespace MauiApp1.Xaml.FlyoutNavigation.Pages;

public partial class AboutPage : ContentPage
{
	public AboutPage()
	{
		InitializeComponent();
	}
}

Pages\SunrisePage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MauiApp1.Xaml.FlyoutNavigation.Pages.SunrisePage"
             Title="SunrisePage">
    <ContentPage.Resources>
        <ResourceDictionary>
            <Style TargetType="Label">
                <Setter Property="HorizontalOptions" Value="Center" />
            </Style>
        </ResourceDictionary>
    </ContentPage.Resources>

    <StackLayout Padding="10">
        <Label x:Name="lblDate" WidthRequest="200" HorizontalTextAlignment="Center" FontAttributes="Bold" />
        <ActivityIndicator x:Name="activityWaiting" IsRunning="False" Color="Orange" />
        <Grid  RowSpacing="10" ColumnSpacing="5" Padding="5">
            <Grid.RowDefinitions>
                <RowDefinition Height="auto" />
                <RowDefinition Height="auto" />
                <RowDefinition Height="auto" />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>

            <Label Text="🌅" FontSize="72" Grid.Column="0" />
            <Label Text="☀️" FontSize="72" Grid.Column="1" />
            <Label Text="🌇" FontSize="72" Grid.Column="2" />

            <Label Text="Sunrise" FontAttributes="Bold" Grid.Column="0" Grid.Row="1" />
            <Label Text="Daylight" FontAttributes="Bold" Grid.Column="1" Grid.Row="1" />
            <Label Text="Sunset" FontAttributes="Bold" Grid.Column="2" Grid.Row="1" />

            <Label x:Name="lblSunrise" WidthRequest="100" FontAttributes="None" Grid.Column="0" Grid.Row="2" HorizontalTextAlignment="Center" />
            <Label x:Name="lblDaylight" WidthRequest="150" FontAttributes="None" Grid.Column="1" Grid.Row="2" HorizontalTextAlignment="Center" />
            <Label x:Name="lblSunset" WidthRequest="100" FontAttributes="None" Grid.Column="2" Grid.Row="2" HorizontalTextAlignment="Center" />
        </Grid>
    </StackLayout>
</ContentPage>

Pages\SunrisePage.xaml.cs

using MauiApp1.Xaml.FlyoutNavigation.Data;

namespace MauiApp1.Xaml.FlyoutNavigation.Pages;

public partial class SunrisePage : ContentPage
{
    ILatLongService LatLongService { get; set; }
    public SunrisePage()
    {
        InitializeComponent();
        LatLongService = new LatLongService();
    }

    protected override async void OnAppearing()
    {
        base.OnAppearing();
        activityWaiting.IsRunning = true;
        var sunriseSunsetData = await GetSunriseSunsetData();
        InitializeUI(sunriseSunsetData.Item1, sunriseSunsetData.Item2, sunriseSunsetData.Item3);
        activityWaiting.IsRunning = false;
    }

    async Task<(DateTime, DateTime, TimeSpan)> GetSunriseSunsetData()
    {

        var latLongData = await LatLongService.GetLatLong();
        var sunData = await new SunriseService().GetSunriseSunsetTimes(latLongData.Latitude, latLongData.Longitude);

        var riseTime = sunData.Sunrise.ToLocalTime();
        var setTime = sunData.Sunset.ToLocalTime();
        var span = setTime.TimeOfDay - riseTime.TimeOfDay;
        return (riseTime, setTime, span);
    }

    void InitializeUI(DateTime riseTime, DateTime setTime, TimeSpan span)
    {
        lblDate.Text = DateTime.Today.ToString("D");
        lblSunrise.Text = riseTime.ToString("h:mm tt");
        lblDaylight.Text = $"{span.Hours} hours, {span.Minutes} minutes";
        lblSunset.Text = setTime.ToString("h:mm tt");
    }
}

 

Data\ILatLongService.cs

namespace MauiApp1.Xaml.FlyoutNavigation.Data;
public interface ILatLongService
{
    Task<(double Latitude, double Longitude)> GetLatLong();
}

Data\LatLongService.cs

namespace MauiApp1.Xaml.FlyoutNavigation.Data;
public class LatLongService : ILatLongService
{
    public async Task<(double Latitude, double Longitude)> GetLatLong()
    {
        var latLoc = 0.0;
        var longLoc = 0.0;

        var status = await Permissions.RequestAsync<Permissions.LocationWhenInUse>();
        if (status == PermissionStatus.Granted)
        {
            var request = new GeolocationRequest(GeolocationAccuracy.Default, TimeSpan.FromSeconds(10));
            var location = await Geolocation.GetLocationAsync(request);
            latLoc = location.Latitude;
            longLoc = location.Longitude;
        }
        return (latLoc, longLoc);
    }
}

Data\MoonPhaseCalculator.cs

namespace MauiApp1.Xaml.FlyoutNavigation.Data;
public static class MoonPhaseCalculator
{
    public enum Phase
    {
        New,
        WaxingCrescent,
        FirstQuarter,
        WaxingGibbous,
        Full,
        WaningGibbous,
        LastQuarter,
        WaningCrescent,
    }

    static double synodicLength = 29.530588853; //length in days of a complete moon cycle
    static DateTime referenceNewMoonDate = new DateTime(2017, 11, 18);

    public static Phase GetPhase(DateTime date)
    {
        return GetPhase(GetAge(date));
    }

    static double GetAge(DateTime date)
    {
        double days = (date - referenceNewMoonDate).TotalDays;

        return days % synodicLength;
    }

    static Phase GetPhase(double age)
    {
        if (age < 1) return Phase.New;
        if (age < 7) return Phase.WaxingCrescent;
        if (age < 8) return Phase.FirstQuarter;
        if (age < 14) return Phase.WaxingGibbous;
        if (age < 15) return Phase.Full;
        if (age < 22) return Phase.WaningGibbous;
        if (age < 23) return Phase.LastQuarter;
        if (age < 29) return Phase.WaningCrescent;

        return Phase.New;
    }
}

Data\SunriseService.cs

using System.Text.Json;

namespace MauiApp1.Xaml.FlyoutNavigation.Data;
public class SunriseService
{
    const string SunriseSunsetServiceUrl = "https://api.sunrise-sunset.org";

    public async Task<(DateTime Sunrise, DateTime Sunset)> GetSunriseSunsetTimes(double latitude, double longitude)
    {
        var query = $"{SunriseSunsetServiceUrl}/json?lat={latitude}&lng={longitude}&date=today";

        var client = new HttpClient();
        client.DefaultRequestHeaders.Add("Accept", "application/json");
        var json = await client.GetStringAsync(query);

        var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
        var data = JsonSerializer.Deserialize<SunriseSunsetData>(json, options);

        return (DateTime.Parse(data.Results.Sunrise), DateTime.Parse(data.Results.Sunset));
    }

    class SunriseSunsetData
    {
#pragma warning disable 0649
        // Field is only set via JSON deserialization, so disable warning that the field is never set.
        //public SunriseSunsetResults Results;
        public SunriseSunsetResults Results { get; set; }
#pragma warning restore 0649
    }

    class SunriseSunsetResults
    {
#pragma warning disable 0649
        // Fields are only set via JSON deserialization, so disable warning that the fields are never set.
        public string Sunrise { get; set; }
        public string Sunset { get; set; }
#pragma warning restore 0649
    }
}

 

Assets 은 다음을 참조하고

Resources/Images 아래에 저장하자

https://github.com/MicrosoftDocs/mslearn-dotnetmaui-create-multi-page-apps/tree/main/exercise1/assets

 

AppShell.xaml page 를 열고

위와 같은 코드가 있다면 삭제 하자

그리고 다음을 추가하자

   <Shell.FlyoutHeader>
        <Grid HeightRequest="100" BackgroundColor="DarkSlateBlue">
            <Image Source="moon.png" />
        </Grid>
    </Shell.FlyoutHeader>
    
    <FlyoutItem Title="Moon Phase" Icon="moon.png">
    <ShellContent
        Title="Home"
        ContentTemplate="{DataTemplate localFlyoutNavigation:MainPage}"
        Route="MainPage" />
    </FlyoutItem>

    <FlyoutItem Title="Sunrise" Icon="sun.png">
        <ShellContent
        ContentTemplate="{DataTemplate localFlyoutNavigation:SunrisePage}"
        Route="SunrisePage" />
    </FlyoutItem>

    <FlyoutItem Title="About" Icon="question.png">
        <ShellContent
        ContentTemplate="{DataTemplate localFlyoutNavigation:AboutPage}"
        Route="AboutPage" />
    </FlyoutItem>
<Shell
...
FlyoutIcon="moon.png">

실행시 Sunrise 화면으로 이동시에 android 같은 경우 access permission 이 필요하다는 오류가 나올수 있다. 

Platforms/Android/MainApplication.cs 에 using 밑에 다음을 추가하자

[assembly: UsesPermission(Android.Manifest.Permission.AccessCoarseLocation)]
[assembly: UsesPermission(Android.Manifest.Permission.AccessFineLocation)]
[assembly: UsesFeature("android.hardware.location", Required = false)]
[assembly: UsesFeature("android.hardware.location.gps", Required = false)]
[assembly: UsesFeature("android.hardware.location.network", Required = false)]
[assembly: UsesPermission(Android.Manifest.Permission.AccessBackgroundLocation)]

 

실행화면

왼쪽 상단 MainPage 옆에 달 아이콘 클릭시

 

 

관련영상

https://youtu.be/JoS-0IdRTB8

 

반응형