zoukankan      html  css  js  c++  java
  • 张高兴的 Xamarin.Forms 开发笔记:为 Android 与 iOS 引入 UWP 风格的汉堡菜单 ( MasterDetailPage )

      所谓 UWP 样式的汉堡菜单,我曾在“张高兴的 UWP 开发笔记:汉堡菜单进阶”里说过,也就是使用 Segoe MDL2 Assets 字体作为左侧 Icon,并且左侧使用填充颜色的矩形用来表示 ListView 的选中。如下图

      但怎样通过 Xamarin.Forms ,将这一样式的汉堡菜单带入到 Android 与 iOS 中呢?

      一、大纲-细节模式简介

      讲代码前首先来说说这种导航模式,官方称“大纲-细节模式”(MasterDetail)。左侧的汉堡菜单称为“大纲”(Master),右侧的页面称为“细节”(Detail)。Xamarin.Froms 为项目提供了若干种导航模式,“大纲-细节”为其中一种。

      二、项目简介

      效果图:

      不多说废话,看代码实在些。

      本示例是使用 Visual Studio 2017 创建的 Cross-Platform 项目,项目名为”HamburgerMenuDemo“,模板为空白项目。(GitHub:https://github.com/ZhangGaoxing/xamarin-forms-demo/tree/master/HamburgerMenuDemo

      待项目创建完成后,解决方案共包含四个项目:共享代码项目、 Android 项目、 iOS 项目、 UWP 项目。共享代码项目为存放共享页面的地方,个人觉得和类库还是有点区别的。

      

      三、共享代码项目 HamburgerMenuDemo 

      首先添加几个页面,根目录下添加一个 MasterPage.xaml 页面,用于”大纲视图“。添加一个 Views 文件夹,用于存放子页面,向其中添加3个界面:Page1、Page2、Page3。添加一个 MasterPageItem.cs 类。

      1. MasterPageItem.cs

      和 UWP 的汉堡菜单一样,首先要创建一个类,作为导航的项目,用来绑定 ListView 。名字叫 MasterPageItem.cs 。

      里面的属性有页面的标题 Title,左侧的图标 Icon,图标的字体 FontFamily,目的页面 DestPage,还有左侧的矩形显示 Selected 与 颜色 Color。由于要实现双向绑定,还要实现接口 INotifyPropertyChanged。要注意的是,Color 类型为 Xamarin.Forms 中的。

    代码如下

    public class MasterPageItem : INotifyPropertyChanged
    {
        // 字体路径,用于引入 Segoe MDL2 Assets 字体
        public string FontFamily { get; set; }
    
        // 字体图标转义
        public string Icon { get; set; }
    
        // 标题
        public string Title { get; set; }
    
        // 目的页
        public Type DestPage { get; set; }
    
        // 用于显示左侧填充矩形,双向绑定
        private bool selected = false;
        public bool Selected
        {
            get { return selected; }
            set
            {
                selected = value;
                this.OnPropertyChanged("Selected");
            }
        }
    
        // 选中颜色,双向绑定 ( using Xamarin.Forms )
        private Color color = new Color();
        public Color Color
        {
            get { return color; }
            set
            {
                color = value;
                this.OnPropertyChanged("Color");
            }
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        public void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

      2. MasterPage.xaml

      MasterPage 为”大纲“视图,即左侧显示 ListView 的页面。本项目的 MasterPage 分为两栏,分一级菜单与二级菜单,即置顶一个 ListView 与置底一个 ListView 。 ListView 的 ItemTemplate 与 UWP 稍有不同,左侧的填充矩形换成了 BoxView,二级菜单的上边线由 Border 换成了高度为1的 BoxView。代码如下

    <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 x:Class="HamburgerMenuDemo.MasterPage"
                 Icon="hamburger.png"
                 Title=" ">
        
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
                <RowDefinition Height="1" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            
            <!--一级菜单-->
            <ListView x:Name="PrimaryListView" VerticalOptions="StartAndExpand" SeparatorVisibility="None">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <ViewCell>
                            <Grid HeightRequest="48">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="48"/>
                                    <ColumnDefinition Width="*" />
                                </Grid.ColumnDefinitions>
                                
                                <BoxView BackgroundColor="{Binding Color}" WidthRequest="5" HeightRequest="26" HorizontalOptions="Start" VerticalOptions="Center" IsVisible="{Binding Selected}" />
                                <Label Text="{Binding Icon}" FontFamily="{Binding FontFamily}" TextColor="{Binding Color}" FontSize="16" HorizontalOptions="Center" VerticalOptions="Center" />
                                <Label Grid.Column="1" Text="{Binding Title}" TextColor="{Binding Color}" FontSize="14" VerticalOptions="Center" />
                            </Grid>
                        </ViewCell>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
    
            <!--BoxView 充当 Border-->
            <BoxView BackgroundColor="Gray" Grid.Row="1" HorizontalOptions="FillAndExpand" />
    
            <!--二级菜单-->
            <ListView x:Name="SecondaryListView" Grid.Row="2" VerticalOptions="End" SeparatorVisibility="None" Margin="0,-6,0,0">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <ViewCell>
                            <Grid HeightRequest="48">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="48"/>
                                    <ColumnDefinition Width="*" />
                                </Grid.ColumnDefinitions>
    
                                <BoxView BackgroundColor="{Binding Color}" WidthRequest="5" HeightRequest="26" HorizontalOptions="Start" VerticalOptions="Center" IsVisible="{Binding Selected}" />
                                <Label x:Name="IconLabel" Text="{Binding Icon}" FontFamily="{Binding FontFamily}" TextColor="{Binding Color}" FontSize="16" HorizontalOptions="Center" VerticalOptions="Center" />
                                <Label Grid.Column="1" Text="{Binding Title}" TextColor="{Binding Color}" FontSize="14" VerticalOptions="Center" />
                            </Grid>
                        </ViewCell>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </Grid>
    </ContentPage>

      MasterPage.xaml.cs 代码也需要讲下,不知是怎么回事,以上 Xaml 代码直接运行时两个菜单会显示不正常,只显示一个菜单,<RowDefinition Height="Auto" /> 在这个 ContentPage 里好像无效。因此我在后台代码设置了二级菜单的高度,也就是48 * secondaryItems.Count。两个 ListView 需要通过属性的方式,向 MainPage 传递控件。字体路径各个项目不同,需要单独设置,我后面会说。MasterPage.xaml.cs 代码如下

    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class MasterPage : ContentPage
    {
        // 向 MainPage 传递控件
        public ListView primaryListView { get { return PrimaryListView; } }
        public ListView secondaryListView { get { return SecondaryListView; } }
    
        public MasterPage()
        {
            InitializeComponent();
    
            // 设置不同平台的字体路径
            string fontFamily;
            switch (Device.RuntimePlatform)
            {
                case "Android":
                    fontFamily = "segmdl2.ttf#Segoe MDL2 Assets";
                    break;
    
                case "iOS":
                    fontFamily = "Segoe MDL2 Assets";
                    break;
    
                case "Windows":
                    fontFamily = "/Assets/segmdl2.ttf#Segoe MDL2 Assets";
                    break;
    
                case "WinPhone":
                    fontFamily = "/Assets/segmdl2.ttf#Segoe MDL2 Assets";
                    break;
    
                default:
                    fontFamily = "segmdl2.ttf#Segoe MDL2 Assets";
                    break;
            }
    
            // 列表项
            var primaryItems = new List<MasterPageItem>() {
                    new MasterPageItem
                    {
                        Title = "Page1",
                        FontFamily = fontFamily,
                        Icon = "xE10F",
                        Color = Color.DeepSkyBlue,
                        Selected = true,
                        DestPage = typeof(Page1)
                    },
                    new MasterPageItem
                    {
                        Title = "Page2",
                        FontFamily = fontFamily,
                        Icon = "xE11F",
                        Color = Color.Black,
                        Selected = false,
                        DestPage = typeof(Page2)
                    },
                    new MasterPageItem
                    {
                        Title = "Page3",
                        FontFamily = fontFamily,
                        Icon = "xE12F",
                        Color = Color.Black,
                        Selected = false,
                        DestPage = typeof(Page2)
                    }
                };
    
            var secondaryItems = new List<MasterPageItem>() {
                    new MasterPageItem
                    {
                        Title = "设置",
                        FontFamily = fontFamily,
                        Icon = "xE713",
                        Color = Color.Black,
                        Selected = false,
                        DestPage = typeof(SettingPage)
                    },
                    new MasterPageItem
                    {
                        Title = "关于",
                        FontFamily = fontFamily,
                        Icon = "xE783",
                        Color = Color.Black,
                        Selected = false,
                        DestPage = typeof(AboutPage)
                    }
                };
    
            // ListView 数据绑定
            PrimaryListView.ItemsSource = primaryItems;
            SecondaryListView.ItemsSource = secondaryItems;
    
            // 设置二级菜单高度
            SecondaryListView.HeightRequest = 48 * secondaryItems.Count;
        }
    }

      3. MainPage.xaml

      下面来修改一下 MainPage.xaml 。MainPage.xaml 为应用的入口页面,可在 App.xaml.cs 中更改。将 MainPage 中的根元素替换为 MasterDetailPage 。注释很详细,不多说了

    <MasterDetailPage xmlns="http://xamarin.com/schemas/2014/forms"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 xmlns:local="clr-namespace:HamburgerMenuDemo"
                 x:Class="HamburgerMenuDemo.MainPage"
                 xmlns:views="clr-namespace:HamburgerMenuDemo.Views">
    
        <!--大纲视图-->
        <MasterDetailPage.Master>
            <!--引入 MasterPage 并给个名称,用于后台设置 MasterPage 传递过来的 ListView-->
            <local:MasterPage x:Name="masterPage" />
        </MasterDetailPage.Master>
        
        <!--细节视图-->
        <MasterDetailPage.Detail>
            <NavigationPage>
                <x:Arguments>
                    <!--默认显示的页面-->
                    <views:Page1 />
                </x:Arguments>
            </NavigationPage>
        </MasterDetailPage.Detail>
    
    </MasterDetailPage>

      同样的 MainPage.xaml.cs 中的代码也很简单,注释很详细

    public MainPage()
    {
        InitializeComponent();
    
        // ListView 点击事件
        masterPage.primaryListView.ItemSelected += MasterPageItemSelected;
        masterPage.secondaryListView.ItemSelected += MasterPageItemSelected;
    
        // 设置 Windows 平台的“大纲”显示模式为折叠
        if (Device.RuntimePlatform == Device.Windows)
        {
            MasterBehavior = MasterBehavior.Popover;
        }
    }
    
    private void MasterPageItemSelected(object sender, SelectedItemChangedEventArgs e)
    {
        var item = e.SelectedItem as MasterPageItem;
    
        if (item != null)
        {
            // 遍历 ListView 数据源,将选中项矩形显示,字体颜色设置成未选中
            foreach (MasterPageItem mpi in masterPage.primaryListView.ItemsSource)
            {
                mpi.Selected = false;
                mpi.Color = Color.Black;
            }
            foreach (MasterPageItem mpi in masterPage.secondaryListView.ItemsSource)
            {
                mpi.Selected = false;
                mpi.Color = Color.Black;
            }
    
            // 设置选中项
            item.Selected = true;
            item.Color = Color.DeepSkyBlue;
    
            // 跳转
            Detail = new NavigationPage((Page)Activator.CreateInstance(item.DestPage));
    
            // 取消 ListView 默认选中样式
            masterPage.primaryListView.SelectedItem = null;
            masterPage.secondaryListView.SelectedItem = null;
    
            // 关闭“大纲”
            IsPresented = false;
        }
    }

      要注意的是 MasterPage.xaml 页面中的 Title 一定要给,要不然会报错,可以在后台 cs 文件中修改 Title 属性,也可以在 Xaml 根元素中修改 Title。Views 中的几个页面 Title 不给可以,但标题栏不会显示页面的 Title,不好看。

      四、Android 项目 HamburgerMenuDemo.Android

      1. 字体设置

      将 segmdl2.ttf 字体文件直接放入 Assets 文件夹下即可

      2. 修改 style.xml

      ”大纲“的默认效果是 DrawerLayout 覆盖状态栏的,不太美观,需要修改样式。在 style.xml 中添加

    <item name="android:fitsSystemWindows">true</item>

      同时,由于修改了样式,变成了状态栏覆盖 DrawerLayout ,需要给 MasterPage.xaml 中的根 Grid 赋值一个 Padding="0,25,0,-6",但 UWP 项目却不需要,这点我会在文末给出代码。

      五、iOS 项目 HamburgerMenuDemo.iOS

      1. 字体设置

      弄了好久,Xamarin 太坑了,plist 的编辑器很不和谐。。。

      (1)将 segmdl2.ttf 字体文件直接放入 Resources 文件夹

      (2)更改 segmdl2.ttf 属性,复制到输出目录 =》 始终复制,生成操作 =》 BundleResource

      (2)不要双击,右击 Info.plist ,查看代码,添加如下内容

    <dict>
        <key>UIAppFonts</key>
        <array>
          <string>segmdl2.ttf</string>
        </array>
      </dict>

      如果要添加其他的资源,可以自己新建一个 .plist 文件,新建的文件是正常显示资源列表的,添加完成后,复制代码到 Info.plist 即可。

      2. Padding

      和安卓一样,需要给 MasterPage.xaml 中的根 Grid 赋值一个 Padding="0,20,0,-6",我会在文末给出代码。

      六、Padding 代码

      在 MasterPage.xaml 添加如下代码

    <!--安卓空出状态栏的宽度-->
    <ContentPage.Resources>
        <ResourceDictionary>
            <OnPlatform x:Key="padding"
                  x:TypeArguments="Thickness"
                  iOS="0,20,0,-6"
                  Android="0,25,0,-6"
                  WinPhone="0" />
        </ResourceDictionary>
    </ContentPage.Resources>

      别忘了在 Grid 中引用资源

    Padding="{StaticResource padding}"

      MasterPage.xaml 最终代码

    <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 x:Class="HamburgerMenuDemo.MasterPage"
                 Icon="hamburger.png"
                 Title=" ">
    
        <!--安卓空出状态栏的宽度-->
        <ContentPage.Resources>
            <ResourceDictionary>
                <OnPlatform x:Key="padding"
                      x:TypeArguments="Thickness"
                      iOS="0,20,0,0"
                      Android="0,20,0,0"
                      WinPhone="0" />
            </ResourceDictionary>
        </ContentPage.Resources>
        
        <Grid Padding="{StaticResource padding}">
            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
                <RowDefinition Height="1" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            
            <!--一级菜单-->
            <ListView x:Name="PrimaryListView" VerticalOptions="StartAndExpand" SeparatorVisibility="None">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <ViewCell>
                            <Grid HeightRequest="48">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="48"/>
                                    <ColumnDefinition Width="*" />
                                </Grid.ColumnDefinitions>
                                
                                <BoxView BackgroundColor="{Binding Color}" WidthRequest="5" HeightRequest="26" HorizontalOptions="Start" VerticalOptions="Center" IsVisible="{Binding Selected}" />
                                <Label Text="{Binding Icon}" FontFamily="{Binding FontFamily}" TextColor="{Binding Color}" FontSize="16" HorizontalOptions="Center" VerticalOptions="Center" />
                                <Label Grid.Column="1" Text="{Binding Title}" TextColor="{Binding Color}" FontSize="14" VerticalOptions="Center" />
                            </Grid>
                        </ViewCell>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
    
            <!--BoxView 充当 Border-->
            <BoxView BackgroundColor="Gray" Grid.Row="1" HorizontalOptions="FillAndExpand" />
    
            <!--二级菜单-->
            <ListView x:Name="SecondaryListView" Grid.Row="2" VerticalOptions="End" SeparatorVisibility="None" Margin="0,-6,0,0">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <ViewCell>
                            <Grid HeightRequest="48">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="48"/>
                                    <ColumnDefinition Width="*" />
                                </Grid.ColumnDefinitions>
    
                                <BoxView BackgroundColor="{Binding Color}" WidthRequest="5" HeightRequest="26" HorizontalOptions="Start" VerticalOptions="Center" IsVisible="{Binding Selected}" />
                                <Label x:Name="IconLabel" Text="{Binding Icon}" FontFamily="{Binding FontFamily}" TextColor="{Binding Color}" FontSize="16" HorizontalOptions="Center" VerticalOptions="Center" />
                                <Label Grid.Column="1" Text="{Binding Title}" TextColor="{Binding Color}" FontSize="14" VerticalOptions="Center" />
                            </Grid>
                        </ViewCell>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </Grid>
    </ContentPage>
  • 相关阅读:
    The Elements of C# Style Design
    The Elements of C# Style Programming
    The Elements of C# Style General Principles
    浅谈CLR
    堆排序
    WPF系列:GridView列绑定控件(一)
    分发服务器迁移(distribute service migration‏)
    通过编码规范来学习一门编程语言
    如何维护数据库中的静态表
    关于短地址服务的一些看法
  • 原文地址:https://www.cnblogs.com/zhanggaoxing/p/7436523.html
Copyright © 2011-2022 走看看