MyException - 我的异常网
当前位置:我的异常网» 操作系统 » 张愉快的 Xamarin.Forms 开发笔记:为 Android 与 i

张愉快的 Xamarin.Forms 开发笔记:为 Android 与 iOS 引入 UWP 风格的汉堡菜单 ( MasterDetailPage )

www.MyException.Cn  网友分享于:2013-10-11  浏览:0次
张高兴的 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>

 

3楼cjw1115
e....汉堡菜单是安卓先引进的
Re: 张高兴
@cjw1115,哈哈,再看看标题
2楼石迪文
学习了
1楼牛肉汤先生
可以互相关注吗?可以转载你的文章吗?,因为我最近也在写有关Xamarin的文章
Re: 张高兴
@牛肉汤先生,ok

文章评论

如何区分一个程序员是“老手“还是“新手“?
如何区分一个程序员是“老手“还是“新手“?
代码女神横空出世
代码女神横空出世
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
如何成为一名黑客
如何成为一名黑客
5款最佳正则表达式编辑调试器
5款最佳正则表达式编辑调试器
我跳槽是因为他们的显示器更大
我跳槽是因为他们的显示器更大
程序员都该阅读的书
程序员都该阅读的书
Java程序员必看电影
Java程序员必看电影
2013年美国开发者薪资调查报告
2013年美国开发者薪资调查报告
10个调试和排错的小建议
10个调试和排错的小建议
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
我的丈夫是个程序员
我的丈夫是个程序员
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
漫画:程序员的工作
漫画:程序员的工作
程序员必看的十大电影
程序员必看的十大电影
那些争议最大的编程观点
那些争议最大的编程观点
程序猿的崛起——Growth Hacker
程序猿的崛起——Growth Hacker
什么才是优秀的用户界面设计
什么才是优秀的用户界面设计
当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
程序员眼里IE浏览器是什么样的
程序员眼里IE浏览器是什么样的
10个帮程序员减压放松的网站
10个帮程序员减压放松的网站
做程序猿的老婆应该注意的一些事情
做程序猿的老婆应该注意的一些事情
老美怎么看待阿里赴美上市
老美怎么看待阿里赴美上市
“肮脏的”IT工作排行榜
“肮脏的”IT工作排行榜
Web开发人员为什么越来越懒了?
Web开发人员为什么越来越懒了?
2013年中国软件开发者薪资调查报告
2013年中国软件开发者薪资调查报告
程序员周末都喜欢做什么?
程序员周末都喜欢做什么?
Google伦敦新总部 犹如星级庄园
Google伦敦新总部 犹如星级庄园
中美印日四国程序员比较
中美印日四国程序员比较
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
编程语言是女人
编程语言是女人
总结2014中国互联网十大段子
总结2014中国互联网十大段子
 程序员的样子
程序员的样子
鲜为人知的编程真相
鲜为人知的编程真相
写给自己也写给你 自己到底该何去何从
写给自己也写给你 自己到底该何去何从
Java 与 .NET 的平台发展之争
Java 与 .NET 的平台发展之争
程序员应该关注的一些事儿
程序员应该关注的一些事儿
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
老程序员的下场
老程序员的下场
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
旅行,写作,编程
旅行,写作,编程
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
每天工作4小时的程序员
每天工作4小时的程序员
我是如何打败拖延症的
我是如何打败拖延症的
那些性感的让人尖叫的程序员
那些性感的让人尖叫的程序员
不懂技术不要对懂技术的人说这很容易实现
不懂技术不要对懂技术的人说这很容易实现
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有