zoukankan      html  css  js  c++  java
  • 使用MvvmCross框架实现Xamarin.Forms的汉堡菜单布局

    注:本文是英文写的,偷懒自动翻译过来了,原文地址:Implementing MasterDetail layout in Xamarin.Forms by MvvmCross

    欢迎大家关注我的公众号:程序员在新西兰,了解美丽的新西兰和码农们的生活

    阅读本文大概需要20分钟。本文目录:

    前言

    通过MvxScaffolding创建项目

    创建MasterDetailPage

    创建MasterPage

    创建DetailPages

    实现菜单功能

    微调UI

    小结

     

     

    前言

    在我的Xamarin和MvvmCross手册中,我展示了使用MvvmCross Framework开发基本Xamarin应用程序的基础知识。在开发真实应用程序时需要考虑更多细节,例如布局,样式和数据库等。例如,汉堡菜单布局是现代移动应用程序中非常常见的导航模式。我们可以使用MasterDetail导航模式来实现汉堡菜单。接下来,我将向您展示如何在Xamarin.Forms应用程序中实现MasterDetail布局。在开始之前,我建议您在这里阅读有关MasterDetailPage的官方文档:Xamarin.Forms Master-Detail Page

    我的开发环境如下所示:

    • Windows 10版本10.0.17134
    • Visual Studio 2017版本15.9.4
    • Xamarin.Forms版本3.4.0.1008975
    • MvvmCross版本6.2.2

    让我们开始吧。

    通过MvxScaffolding创建项目

    如果您是MvvmCross的新手,使用MvvmCross创建Xamarin应用程序可能有点棘手。幸运的是,我们有一些项目模板来简化我们的工作。您可以在官方文档中找到它们:MvvmCross入门我建议你使用这个:MvxScaffolding它是新的,支持.net标准。您可以通过单击VS 2017中的工具 - 扩展和更新来搜索它,如下所示:

     

    安装后,您可以在MvvmCross类别中创建一个新的Xamarin.Forms应用程序:

     

    输入MvxFormsMasterDetailDemo为项目名称。MvxScaffolding为我们提供了一个非常友好的界面来定制应用程序。为了更好地理解,我们选择Blank模板,如下所示:

     

    默认设置不包含UWP项目。如果您需要支持UWP平台,请选择它,并选择Min SDK版本为1803.由于旧的Windows 10版本不支持某些新功能,因此建议此时使用。此外,您需要输入描述作为UWP应用程序名称。

     

    单击NEXT按钮,您将看到一个摘要窗口。检查所有信息,然后单击DONE按钮。MvxScaffolding将生成一个具有良好结构的基本空白Xamarin.Forms应用程序。

    创建MasterDetailPage

    MasterDetailPage是应用程序的根页面。实际上,它是一个MasterDetailPage的实例它不应该用作子页面以确保在不同平台上的一致用户体验。

    创建ViewModel

    接下来,添加在MvxFormsMasterDetailDemo.Core项目MasterDetailViewModel中的ViewModels文件夹中调用的新类文件将其更改为从MvxViewModel继承通常,我们还需要使用它NavigationService来实现ViewModel中的导航。因此,IMvxNavigationService通过使用依赖注入注入实例

    using MvvmCross.Navigation;
    using MvvmCross.ViewModels;
    
    namespace MvxFormsMasterDetailDemo.Core.ViewModels
    {
        public class MasterDetailViewModel : MvxViewModel
        {
            readonly IMvxNavigationService _navigationService;
    
            public MasterDetailViewModel(IMvxNavigationService navigationService)
            {
                _navigationService = navigationService;
            }
        }
    }

    创建XAML文件

    Xamarin.Forms为我们提供了一些导航模式,包括分层导航,选项卡式页面,MasterDetailPage和模态页面等。根据我们的要求,我们希望在主页面上有一个汉堡菜单。所以我们可以使用MasterDetailPage,它是应用程序的根页面,包含两个区域:左边是MasterPage,右边是DetailPage。我们可以将菜单放在MasterPage中。单击菜单项时,导航服务将在DetailPage区域中显示另一页。

    在MvvmCross中,Xamarin.Forms中有MvxFromsPagePresenter不同的页面类型,它们定义了视图的显示方式。我们MvxPagePresentationAttribute用来指定不同的页面类型。有关更多详细信息,请在此处查看文档:Xamarin.Forms查看演示者

    App.cs在MvxFormsMasterDetailDemo.Core项目中打开该文件。请注意,框架将从HomeViewModel第一页开始现在让我们创建一个MasterDetailPage并用它来替换第一页。

    右键单击MvxFormsMasterDetailDemo.UI项目中的Pages文件夹,然后选择AddNew ItemContent Page从Xamarin.Forms类别中选择,如下所示:

     

    打开MasterDetailPage.xaml文件。请注意,此页面是一个ContentPage我们需要将其改为继承MvxMasterDetailPage用以下代码替换XAML代码:

    <?xml version =“1.0”encoding =“utf-8”?> 
    <views:MvxMasterDetailPage xmlns =“http://xamarin.com/schemas/2014/forms” 
                 xmlns:x =“http://schemas.microsoft .com / winfx / 2009 / xaml“ 
                 x:Class =”MvxFormsMasterDetailDemo.UI.Pages.MasterDetailPage“ 
                 xmlns:views =”clr-namespace:MvvmCross.Forms.Views; assembly = MvvmCross.Forms“ 
                 xmlns:viewModels =”clr- namespace:MvxFormsMasterDetailDemo.Core.ViewModels; assembly = MvxFormsMasterDetailDemo.Core“ 
                 x:TypeArguments =”viewModels:MasterDetailViewModel“> 
    </ views:MvxMasterDetailPage>

    我们MvxMasterDetailPage用来替换默认ContentPage类型。为此,我们需要添加以下代码:

    xmlns:views="clr-namespace:MvvmCross.Forms.Views;assembly=MvvmCross.Forms"

    要设置MasterDetailPage的ViewModel,我们需要指定x:TypeArgumentsviewModels:MasterDetailViewModel不要忘记通过添加以下代码导入viewModels命名空间:xmlns:viewModels="clr-namespace:MvxFormsMasterDetailDemo.Core.ViewModels;assembly=MvxFormsMasterDetailDemo.Core"

    打开MasterDetailPage.xaml.cs文件,将其基类替换ContentPageMvxMasterDetailPage<MasterDetailViewModel>MvxMasterDetailPagePresentation属性添加到类中,如下面的代码:

    using MvvmCross.Forms.Presenters.Attributes;
    using MvvmCross.Forms.Views;
    using MvxFormsMasterDetailDemo.Core.ViewModels;
    using Xamarin.Forms.Xaml;
    
    namespace MvxFormsMasterDetailDemo.UI.Pages
    {
        [XamlCompilation(XamlCompilationOptions.Compile)]
        [MvxMasterDetailPagePresentation(Position = MasterDetailPosition.Root, WrapInNavigationPage = false, Title = "MasterDetail Page")]
        public partial class MasterDetailPage : MvxMasterDetailPage<MasterDetailViewModel>
        {
            public MasterDetailPage()
            {
                InitializeComponent();
            }
        }
    }

    我们来看看MvxMasterDetailPagePresentation属性。有一些非常重要的属性MvxMasterDetailPagePresentationPosition是一个枚举值,用于指示页面的类型,在此处设置为Root。请设置如图所示的其他属性,否则,您可能会得到一些奇怪的结果。

    创建MasterPage

    MasterPage用于显示汉堡包菜单,ContentPage其中包含一个ListView我们将使用数据绑定来初始化菜单项。

    创建ViewModel

    在MvxFormsMasterDetailDemo.Core项目ViewModels文件夹中创建一个类MenuViewModel使用以下代码替换内容:

    using System.Collections.ObjectModel;
    using MvvmCross.Navigation;
    using MvvmCross.ViewModels;
    
    namespace MvxFormsMasterDetailDemo.Core.ViewModels
    {
        public class MenuViewModel : MvxViewModel
        {
            readonly IMvxNavigationService _navigationService;
    
            public MenuViewModel(IMvxNavigationService navigationService)
            {
                _navigationService = navigationService;
                MenuItemList = new MvxObservableCollection<string>()
                {
                    "Contacts",
                    "Todo"
                };
            }
    
            #region MenuItemList;
            private ObservableCollection<string> _menuItemList;
            public ObservableCollection<string> MenuItemList
            {
                get => _menuItemList;
                set => SetProperty(ref _menuItemList, value);
            }
            #endregion
        }
    }

    它有一个MenuItemList用来存储一些菜单项属性。为简单起见,只有两个字符串:ContactsTodo我们还需要IMvxNavigationService在构造函数中注入实例

    创建XAML文件

    接下来,在MvxFormsMasterDetailDemo.UI项目中的Pages文件夹中,添加一个新的ContentPage,命名为MenuPage.xaml打开MenuPage.xaml文件并使用以下代码替换内容:

    <?xml version="1.0" encoding="utf-8" ?>
    <views:MvxContentPage xmlns="http://xamarin.com/schemas/2014/forms"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 xmlns:views="clr-namespace:MvvmCross.Forms.Views;assembly=MvvmCross.Forms"
                 xmlns:viewModels="clr-namespace:MvxFormsMasterDetailDemo.Core.ViewModels;assembly=MvxFormsMasterDetailDemo.Core"
                 x:Class="MvxFormsMasterDetailDemo.UI.Pages.MenuPage"
                 x:TypeArguments="viewModels:MenuViewModel" >
        <ContentPage.Content>
            <StackLayout>
                <ListView></ListView>
            </StackLayout>
        </ContentPage.Content>
    </views:MvxContentPage>

    打开MenuPage.xaml.cs文件并设置基类和属性,如下所示:

    using MvvmCross.Forms.Presenters.Attributes;
    using MvvmCross.Forms.Views;
    using MvxFormsMasterDetailDemo.Core.ViewModels;
    using Xamarin.Forms.Xaml;
    
    namespace MvxFormsMasterDetailDemo.UI.Pages
    {
        [XamlCompilation(XamlCompilationOptions.Compile)]
        [MvxMasterDetailPagePresentation(Position = MasterDetailPosition.Master, WrapInNavigationPage = false, Title = "HamburgerMenu Demo")]
        public partial class MenuPage : MvxContentPage<MenuViewModel>
        {
            public MenuPage ()
            {
                InitializeComponent ();
            }
        }
    }

    MvxMasterDetailPagePresentation的属性Position应该被设置为Master,这意味着该页面将被显示为MasterDetailPage的Master。MasterPage还有另一个陷阱:必须设置Title属性,否则,您的应用程序将被卡住。因此,您必须设置MvxMasterDetailPagePresentation属性的Title属性。

    现在我们需要设置数据绑定ListView我们已经在ViewModel有MenuItemList,所以我们现在要做的就是设置ListView的ItemsSource,如下所示:

    <ListView ItemsSource="{Binding MenuItemList}">
        <ListView.ItemTemplate>
            <DataTemplate>
                <TextCell Text="{Binding}"></TextCell>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>

    目前,我们只是使用TextCell来显示菜单文本。在我们实现汉堡包菜单的全部功能之前,让我们创建DetailPages。

    创建DetailPages

    为简单起见,我们只添加两个页面作为详细信息页面。

    创建ViewModels

    MvxFormsMasterDetailDemo.Core项目的ViewModels文件夹中添加两个名为ContactsViewModel和TodoViewModel的新文件让它们分别从MvxViewModel继承

    using MvvmCross.ViewModels;
    
    namespace MvxFormsMasterDetailDemo.Core.ViewModels
    {
        public class ContactsViewModel : MvxViewModel
        {
        }
    }
    using MvvmCross.ViewModels;
    
    namespace MvxFormsMasterDetailDemo.Core.ViewModels
    {
        public class TodoViewModel : MvxViewModel
        {
        }
    }

    创建XAML文件

    将两个ContentPage文件添加到MvxFormsMasterDetailDemo.UI项目的Pages文件夹中,并将它们命名为ContactsPage.xamlTodoPage.xaml要使用MvvmCross功能,我们需要将它们更改为继承自MvxContentPage打开ContactsPage.xaml文件并使用以下代码替换内容:

    <?xml version="1.0" encoding="utf-8" ?>
    <views:MvxContentPage xmlns="http://xamarin.com/schemas/2014/forms"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 x:Class="MvxFormsMasterDetailDemo.UI.Pages.ContactsPage"
                 xmlns:views="clr-namespace:MvvmCross.Forms.Views;assembly=MvvmCross.Forms"
                 xmlns:viewModels="clr-namespace:MvxFormsMasterDetailDemo.Core.ViewModels;assembly=MvxFormsMasterDetailDemo.Core"
                 x:TypeArguments="viewModels:ContactsViewModel">
        <ContentPage.Content>
            <StackLayout>
                <Label Text="Welcome to ContactsPage!"
                    VerticalOptions="CenterAndExpand" 
                    HorizontalOptions="CenterAndExpand" />
            </StackLayout>
        </ContentPage.Content>
    </views:MvxContentPage>

    Label用于指示当前页面。

    打开ContactsPage.xaml.cs文件并更新内容,如下所示:

    using MvvmCross.Forms.Presenters.Attributes;
    using MvvmCross.Forms.Views;
    using MvxFormsMasterDetailDemo.Core.ViewModels;
    using Xamarin.Forms.Xaml;
    
    namespace MvxFormsMasterDetailDemo.UI.Pages
    {
        [XamlCompilation(XamlCompilationOptions.Compile)]
        [MvxMasterDetailPagePresentation(Position = MasterDetailPosition.Detail, NoHistory = true, Title = "Contacts Page")]
        public partial class ContactsPage : MvxContentPage<ContactsViewModel>
        {
            public ContactsPage ()
            {
                InitializeComponent ();
            }
        }
    }

    Position属性的值MasterDetailPosition.Detail,这意味着此页面应位于MasterDetailPage的Detail区域。NoHistory属性应该是true,用来保证针对不同平台的导航没有奇怪的行为。Title属性用于在页面顶部显示页面名称。

    TodoPage.xamlTodoPage.xaml.cs做同样的更改不要忘记更新Label控件的Text以显示页面名称。

    实现菜单功能

    现在,我们有我们需要显示所有的页面:根页叫MasterDetailPage,一个MasterPage叫做MenuPage和两个DetailPages叫做ContactsPageTodoPage接下来,我们需要使菜单正常工作。

    显示MasterPage和DetailPage

    打开MasterDetailViewModel.cs文件并覆盖ViewAppearing方法,如下所示:

    public override async void ViewAppearing()
            {
                base.ViewAppearing();
                await _navigationService.Navigate<MenuViewModel>();
                await _navigationService.Navigate<ContactsViewModel>();
            }

    ContactsPage应用程序启动时被用作DetailPage。因为我们已经MenuPage和ContactsPage指定了属性MvxMasterDetailPagePresentation,所以MvvmCross会找到并将它们显示在正确位置。

    在MvxFormsMasterDetailDemo.Core项目中打开App.cs文件,并将第一页替换为MasterDetailPage

    public class App : MvxApplication
        {
            public override void Initialize()
            {
                RegisterAppStart<MasterDetailViewModel>();
            }
        }

    现在我们可以为三个平台启动应用程序:

    安卓:

     
     

    默认视图很好。Xamarin.Forms会自动在页面左上角添加一个汉堡包图标按钮。当我们单击按钮时,菜单显示,但没有页眉。我们稍后会调整UI。

    iOS版:

     
     

    iOS的默认视图与Android不同。页面上没有汉堡包图标。关于MenuPage标题栏的另一个问题与Android相同。看起来我们需要添加一个汉堡图标并显示头部标题栏。我们稍后会这样做。

    UWP:

     

    发生了什么?MasterPage自动显示,但没有默认的汉堡包按钮。

    有关MasterDetailPage导航行为的详细信息,请在此处阅读:MasterDetailPage概述根据文档,母版页应该有一个包含按钮的导航栏。但现在我们得到了一些不同的结果。无论如何,我们可以自己解决它。

    要修复UWP的布局,只需设置MasterBehaviorMasterDetailPage 属性即可。它是一个枚举值,用于确定详细信息页面在MasterDetailPage中的显示方式。如果保留它Default,它将分别显示不同平台的DetailPage。这就是我们得到不同结果的原因。

    MasterDetailPage.xaml在MvxFormsMasterDetailDemo.UI项目中打开该文件。MasterBehavior在页面定义中添加属性并将其设置为Popover,这意味着DetailPage将覆盖或部分覆盖MasterPage:

    <?xml version="1.0" encoding="utf-8" ?>
    <views:MvxMasterDetailPage xmlns="http://xamarin.com/schemas/2014/forms"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 x:Class="MvxFormsMasterDetailDemo.UI.Pages.MasterDetailPage"
                 xmlns:views="clr-namespace:MvvmCross.Forms.Views;assembly=MvvmCross.Forms"
                 xmlns:viewModels="clr-namespace:MvxFormsMasterDetailDemo.Core.ViewModels;assembly=MvxFormsMasterDetailDemo.Core"
                 x:TypeArguments="viewModels:MasterDetailViewModel" MasterBehavior="Popover">
    
    </views:MvxMasterDetailPage>

    要查看效果,请运行UWP项目,它看起来像这样:

     
     

    现在它在页面左上方有默认的汉堡包按钮。运行Android和iOS项目以确保所有内容都不会因轻微更改而中断。您可能会注意到这三个平台之间仍存在一些差异。例如,UWP项目有标题栏,但Android和iOS没有。Android和UWP有默认的汉堡包按钮,但iOS没有。我们稍后会修复它们。

    设置菜单导航

    单击菜单项时,应用程序应显示正确的DetailPage。现在让我们设置Command菜单项。在MvxFormsMasterDetailDemo.Core项目中打开MenuViwModel.cs文件,然后添加一个Command,如下所示:

    #region ShowDetailPageAsyncCommand;
            private IMvxAsyncCommand<string> _showDetailPageAsyncCommand;
            public IMvxAsyncCommand<string> ShowDetailPageAsyncCommand
            {
                get
                {
                    _showDetailPageAsyncCommand = _showDetailPageAsyncCommand ?? new MvxAsyncCommand<string>(ShowDetailPageAsync);
                    return _showDetailPageAsyncCommand;
                }
            }
            private async Task ShowDetailPageAsync(string param)
            {
                // Implement your logic here.
            }
            #endregion

    这是一个实例IMvxAsyncCommand<T>,其中包含来自数据绑定的参数。参数的类型是string,因为我们知道MenuItemListis中的对象是string类型如果您MenuItemList 有其他一些泛型类型,请记住将泛型类型更改为您的实际项类型。

    然后我们需要为命令设置数据绑定。MenuPage.xaml在MvxFormsMasterDetailDemo.UI项目中打开该文件并检查当前ItemTemplate

    <ListView ItemsSource="{Binding MenuItemList}">
        <ListView.ItemTemplate>
            <DataTemplate>
                <TextCell Text="{Binding}"></TextCell>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>

    这里我们只使用一个简单的TextCell来显示菜单文本。如何将命令绑定到ListView

    在我们开始数据绑定之前,我建议您阅读本文:Xamarin.Forms Command 接口在Xamarin.Forms中,一些控件原生支持Command,比如ButtonMenuItemTextCell和一些继承自它们的类。并且,SearchBar支持SearchCommand,实际上也是一种ICommand类型的属性ListView的RefreshCommand属性也是ICommand接口的实例

    对于那些不直接支持ICommand的控件,Xamarin.Forms提供了一个TapGestureRecognizer来支持Command绑定。有关详细信息,请阅读以下文章:添加点按手势识别器请记住,虽然GestureRecognizer支持多种手势,如pinchpanswipe,但只TapGestureRecognizer支持ICommand另一个限制是视图元素必须支持GestureRecognizers

    现在让我告诉你如何使用TapGestureRecognizer绑定Command到菜单项。首先,设置MenuPage的x:Name属性

    <views:MvxContentPage xmlns="http://xamarin.com/schemas/2014/forms"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 xmlns:views="clr-namespace:MvvmCross.Forms.Views;assembly=MvvmCross.Forms"
                 xmlns:viewModels="clr-namespace:MvxFormsMasterDetailDemo.Core.ViewModels;assembly=MvxFormsMasterDetailDemo.Core"
                 x:Class="MvxFormsMasterDetailDemo.UI.Pages.MenuPage"
                 x:TypeArguments="viewModels:MenuViewModel" 
                 x:Name="MainContent">

    我们需要名称来引用页面的当前ViewModel。

    更新ItemTemplate如下:

    <ListView.ItemTemplate>
        <DataTemplate>
            <ViewCell>
                <StackLayout Padding="10">
                    <StackLayout.GestureRecognizers>
                        <TapGestureRecognizer 
                            Command="{Binding BindingContext.DataContext.ShowDetailPageAsyncCommand, Source={x:Reference MainContent}}"
                            CommandParameter="{Binding}">
                        </TapGestureRecognizer>
                    </StackLayout.GestureRecognizers>
                    <Label Text="{Binding}" VerticalOptions="Center"></Label>
                </StackLayout>
            </ViewCell>
        </DataTemplate>
    </ListView.ItemTemplate>

    我们对ItemTemplate做了一些修改

    首先,用ViewCell替换默认的TextCellViewCell为我们提供了更多自定义UI的灵活性。所以我们可以随意定义ItemTemplate例如,我们可能会为每个菜单项添加一个图标。

    ViewCell元素中,使用一个StackLayout控件作为容器,它支持GestureRecognizers,所以我们可以将TapGestureRecognizer添加StackLayout

    TapGestureRecognizer元素中,我定义了两个重要的属性,一个是Command,另一个是CommandParameter正如我在之前的文章中所说,你必须非常清楚DataContext你绑定到你的观点。对于我们的情况下,我必须找到MenuViewModel中的命令ShowDetailPageAsyncCommand,所以我用Source={x:Reference MainContent}获取源对象,这是一个当前的名为MainContent的页面现在我们可以获取页面的ViewModel,也就是 BindingContext.DataContext,然后将BindingContext.DataContext.ShowDetailPageAsyncCommand用作绑定路径。我对BindingContext.DataContext有点困惑,因为它与UWP中的语法不同。请注意,完整语法是:

    Command =“{Binding Path = BindingContext.DataContext.ShowDetailPageAsyncCommand,Source = {x:Reference MainContent}}”

    当Path作为命令绑定的第一个参数添加时,可以被删除。

    对于CommandParameter,它更容易。只需将当前字符串绑定到它。所以它是CommandParameter="{Binding}"如果您使用包含某些属性的对象,请使用CommandParameter="{Binding YourProperty}"

    接下来,让我们更新命令。再次打开MvxFormsMasterDetailApp.Core项目中ViewModels文件夹的MenuViewModel.cs文件,并完成该ShowDetailPageAsync方法,如下面的代码:

    private async Task ShowDetailPageAsync(string param)
            {
                // Implement your logic here.
                switch (param)
                {
                    case "Contacts":
                        await mvxNavigationService.Navigate<ContactsViewModel>();
                        break;
                    case "Todo":
                        await mvxNavigationService.Navigate<TodoViewModel>();
                        break;
                    default:
                        break;
                }
            }
            #endregion

    此方法接收来自命令绑定的参数。因此,我们可以确定哪个页面应显示为DetailPage。

    现在启动应用程序并观察导航行为。还有一个问题。单击菜单项时,虽然DetailPage显示正确,但MenuPage仍覆盖DetailPage。所以我们必须控制MasterPage的导航行为。为此,我们需要将Xamarin.Forms安装到MvxFormsMasterDetailDemo.Core项目。您可以通过Xamarin.Forms在NuGet包管理器中搜索来安装它请与其他项目安装相同版本的Xamarin.Forms以避免引用错误。对于我的演示解决方案,我使用Xamarin.Forms.3.4.0.1008975

    ShowDetailPageAsyncswitch之后的方法中添加一些代码(这段代码来自https://github.com/MvvmCross/MvvmCross/issues/2995)

    if (Application.Current.MainPage is MasterDetailPage masterDetailPage)
    {
        masterDetailPage.IsPresented = false;
    }
    else if (Application.Current.MainPage is NavigationPage navigationPage
             && navigationPage.CurrentPage is MasterDetailPage nestedMasterDetail)
    {
        nestedMasterDetail.IsPresented = false;
    }

    IsPresented用于控制是否显示母版页。要隐藏MasterPage,请将其设置为false有关更多详细信息,请在此处阅读:创建和显示详细信息页面

    启动所有三个平台的应用程序,以确保菜单正常工作。

    设置数据绑定的其他方法

    使用TextCell的固有命令

    XAML世界的数据绑定机制是灵活的。实际上,我们有多种方法来实现我们的目标。例如,如果您仅用TextCell显示菜单项,则可以使用简单的方法进行导航。正如我在上一节中所说,TextCell原生支持ICommand所以我们可以使用这样的数据绑定语法:

    <DataTemplate>
        <TextCell Text="{Binding}" Command="{Binding BindingContext.DataContext.ShowDetailPageAsyncCommand, Source={x:Reference MainContent}}" CommandParameter="{Binding}"></TextCell>
    </DataTemplate>

    这种方式更容易。当用户点击时TextCell,它会触发Command但缺点是您无法自定义菜单项的UI。TextCell只支持文字。如果要添加一些图像或定义复杂的项目布局,则必须使用ViewCell

    使用Bahaviors

    此外,您可能认为我们可以使用ItemSelectedItemTapped事件。当然,我们可以!但不幸的是,这些事件没有实现ICommand接口,所以我们不能直接使用数据绑定。要使用ICommand绑定,我们需要使用a Behavior将事件转换为命令,如下所述:可重用的EventToCommandBehavior

    您可能不熟悉行为。行为来自Blend SDK,它是XAML世界中非常有用的库。可以将这些行为附加到某些控件并侦听某些事件,然后在ViewModel中调用某些命令。这是为那些未设计为与Command交互的控件添加Command模式支持的好方法因此,我们可以优雅地使用MVVM模式,而不是在代码隐藏文件中使用事件处理程序。

    您可以按照官方文档中的说明创建您的EventToCommandBehavior,但我们可以利用第三方库快速完成:Behaviors.Xamarin.Forms.Netstandard它不是官方项目,但易于使用。您可以通过搜索Behaviors.Xamarin.FormsNuGet包管理器将其安装到MvxFormsMasterDetailApp.UI项目

     

    我们可以使用此库来使ListView控件在选择项目时在ViewModel中触发我们的命令。为此,MenuViewModel.cs文件中添加叫做SelectedMenuItem的可绑定属性,该属性用于指示当前所选项,如下所示:

    #region SelectedMenuItem;
    private string _selectedMenuItem;
    public string SelectedMenuItem
    {
        get => _selectedMenuItem;
        set => SetProperty(ref _selectedMenuItem, value);
    }
    #endregion

    ShowDetailPageAsyncCommand下面的代码替换我们在上一节中创建区域:

    #region ShowDetailPageAsyncCommand;
            private IMvxAsyncCommand _showDetailPageAsyncCommand;
            public IMvxAsyncCommand ShowDetailPageAsyncCommand
            {
                get
                {
                    _showDetailPageAsyncCommand = _showDetailPageAsyncCommand ?? new MvxAsyncCommand(ShowDetailPageAsync);
                    return _showDetailPageAsyncCommand;
                }
            }
            private async Task ShowDetailPageAsync()
            {
                // Implement your logic here.
                switch (SelectedMenuItem)
                {
                    case "Contacts":
                        await _navigationService.Navigate<ContactsViewModel>();
                        break;
                    case "Todo":
                        await _navigationService.Navigate<TodoViewModel>();
                        break;
                    default:
                        break;
                }
                if (Application.Current.MainPage is MasterDetailPage masterDetailPage)
                {
                    masterDetailPage.IsPresented = false;
                }
                else if (Application.Current.MainPage is NavigationPage navigationPage
                         && navigationPage.CurrentPage is MasterDetailPage nestedMasterDetail)
                {
                    nestedMasterDetail.IsPresented = false;
                }
            }
            #endregion

    你找到了区别吗?我从命令中删除了参数,并ShowDetailPageAsync方法中使用了SelectedMenuItem属性 接下来,我们需要为ListView的SelectedItem设置数据绑定在MvxFormsMasterDetailApp.UI项目的Pages文件夹中打开MenuPage.xaml文件,删除当前ListView控件,然后添加一个新文件ListView,如下所示:

    <ListView x:Name="MenuList" ItemsSource="{Binding MenuItemList}" 
              SelectedItem="{Binding SelectedMenuItem, Mode=TwoWay}">
        <ListView.ItemTemplate>
            <DataTemplate>
                <TextCell Text="{Binding}"></TextCell>
                </DataTemplate>
            </ListView.ItemTemplate>
    </ListView>

    通过以下代码SelectedItem="{Binding SelectedMenuItem, Mode=TwoWay}",我们可以在ViewModel 中设置ListView的SelectedItem与SelectedMenuItem属性之间的双向数据绑定

    在views:MvxContentPage定义中导入Behavior命名空间xmlns:behaviors="clr-namespace:Behaviors;assembly=Behaviors"现在我们可以使用behaviors前缀来使用库中的行为。更新ListView如下所示的XMAL 

    <ListView x:Name="MenuList" ItemsSource="{Binding MenuItemList}" 
              SelectedItem="{Binding SelectedMenuItem, Mode=TwoWay}">
        <ListView.Behaviors>
            <behaviors:EventHandlerBehavior EventName="ItemSelected">
                <behaviors:InvokeCommandAction 
                    Command="{Binding BindingContext.DataContext.ShowDetailPageAsyncCommand, 
                    Source={x:Reference MainContent}}"></behaviors:InvokeCommandAction>
                </behaviors:EventHandlerBehavior>
        </ListView.Behaviors>
        <ListView.ItemTemplate>
            <DataTemplate>
                <TextCell Text="{Binding}"></TextCell>
                </DataTemplate>
            </ListView.ItemTemplate>
    </ListView>

    ListView控件放置了一个Behaviors部分有一个被调用的行为EventHandlerBehavior,它将被ItemSelected事件触发在Behaviors中,有一个InvokeCommandAction,它将调用ViewModel中的ShowDetailPageAsyncCommand请注意数据绑定语法。我们需要指定绑定SourcePath绑定。如果你只是使用{Binding ShowDetailPageAsyncCommand}它,它将无法正常工作。所以要小心当前控件的BindingContext

    运行三个平台的应用程序,您将看到它按预期工作。您可以选择任何方法来实现菜单功能。我只想告诉你如何以不同的方式做到这一点。也许你会将它们用于其他场景。

    微调UI

    不同平台的UI存在一些缺陷。例如,iOS的页眉和汉堡菜单图标不如我们预期的那么好。让我们解决它们。

    添加iOS的汉堡包图标

    根据MasterDetailPage的官方文档,我认为iOS也应该显示像Android和UWP这样的按钮,但事实并非如此。我们可以Icon为MasterPage 设置属性。

    此处下载图像文件将其粘贴到MvxFormsMasterDetailDemo.iOS项目的Resources文件夹中。如果没有这样的文件夹,请创建一个。图像的Build Action属性应该是BundleResource

    在MvxFormsMasterDetailApp.UI项目的Pages文件夹中打开MenuPage.xaml文件将以下代码添加到以下views:MvxContentPage部分:Icon="hamburger.png"现在启动iOS应用程序:

     

    那很好!

    为Android和iOS添加标题栏

    UWP将为MasterPage添加默认标题栏。对于Android和iOS,我们需要分别定义它。

    为了为不同的平台提供一些特定的值,我们可以使用Device类,该类包含许多属性和方法,可以帮助我们自定义特定平台的布局和功能。您可以在此处阅读有关它的详细信息:Xamarin.Forms设备类

    根据我们的要求,我们只需要为Android和iOS添加标题栏。MvxFormsMasterDetailDemo.UI项目的Pages文件夹中打开MenuPage.xaml文件ListView定义之前添加以下代码

    <StackLayout HeightRequest="40">
        <StackLayout.IsVisible>
            <OnPlatform x:TypeArguments="x:Boolean">
                <On Platform="Android, iOS" Value="True" />
                <On Platform="UWP" Value="False" />
            </OnPlatform>
        </StackLayout.IsVisible>
        <Label Text="My HamburgerMenu Demo" Margin="10" VerticalOptions="Center" FontSize="Large"></Label>
    </StackLayout>

    实际上,OnPlatform标记正在做一些类似switch在代码中创建语句的东西它包含几个On类型来接收Platform属性,表示当前平台。有一些不同的值,以确定不同的平台:iOSAndroidUWPmacOS因此,对Android和iOS来说,我们可以通过设置其属性来创建一个包含Label控件StackLayout并设置其其IsVisible属性以显示应用名称但对于UWP来说,它是看不见的。这意味着添加代码不会对UWP进行任何更改。

    运行适用于Android和iOS的应用。它适用于Android。但在iOS平台上,标题栏稍微覆盖了手机的状态栏,如下所示:

     

    我们可以为StackLayout添加一些Margin为iOS 添加另一个OnPlatform标记,如下所示:

    <StackLayout.Margin>
        <OnPlatform x:TypeArguments="Thickness">
            <On Platform="iOS" Value="0,20,0,0" />
        </OnPlatform>
    </StackLayout.Margin>

    它只影响iOS的UI。现在来看看所有平台:

    iOS版:

     

    安卓:

     

    UWP:

     

    好的,一切都很好,除了UWP的列表项高度......

    调整UWP项目的高度

    您可能会注意到,如果我们将其TextCell用作ListView的项模板,则Android和iOS的ListView的项都有默认边距和样式但对于UWP平台,项没有默认样式和适当高度。让我们定义项目模板的样式。同时,我们应该确保它适用于每个平台。

    打开MvxFormsMasterDetailDemo.UI项目中的Pages文件夹中MenuPage.xaml文件。通过以下代码更新ItemTemplate

    <ListView.ItemTemplate>
        <DataTemplate>
            <ViewCell>
                <StackLayout HeightRequest="50">
                    <Label Text="{Binding}" Margin="20,0,0,0" 
                           VerticalOptions="CenterAndExpand"></Label>
                </StackLayout>
            </ViewCell>
        </DataTemplate>
    </ListView.ItemTemplate>

    这就对了。最后,整个文件看起来像这样:

    <?xml version="1.0" encoding="utf-8" ?>
    <views:MvxContentPage xmlns="http://xamarin.com/schemas/2014/forms"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 xmlns:views="clr-namespace:MvvmCross.Forms.Views;assembly=MvvmCross.Forms"
                 xmlns:viewModels="clr-namespace:MvxFormsMasterDetailDemo.Core.ViewModels;assembly=MvxFormsMasterDetailDemo.Core"
                 x:Class="MvxFormsMasterDetailDemo.UI.Pages.MenuPage"
                 x:TypeArguments="viewModels:MenuViewModel" 
                 x:Name="MainContent"
                 xmlns:behaviors="clr-namespace:Behaviors;assembly=Behaviors"
                 Icon="hamburger.png">
        <ContentPage.Content>
            <StackLayout>
                <StackLayout HeightRequest="40">
                    <StackLayout.IsVisible>
                        <OnPlatform x:TypeArguments="x:Boolean">
                            <On Platform="Android, iOS" Value="True" />
                            <On Platform="UWP" Value="False" />
                        </OnPlatform>
                    </StackLayout.IsVisible>
                    <StackLayout.Margin>
                        <OnPlatform x:TypeArguments="Thickness">
                            <On Platform="iOS" Value="0,20,0,0" />
                        </OnPlatform>
                    </StackLayout.Margin>
                    <Label Text="HamburgerMenu Demo" Margin="10" VerticalOptions="Center" FontSize="Large"></Label>
                </StackLayout>
                <ListView x:Name="MenuList" ItemsSource="{Binding MenuItemList}" 
                          SelectedItem="{Binding SelectedMenuItem, Mode=TwoWay}">
                    <ListView.Behaviors>
                        <behaviors:EventHandlerBehavior EventName="ItemSelected">
                            <behaviors:InvokeCommandAction 
                                Command="{Binding BindingContext.DataContext.ShowDetailPageAsyncCommand, 
                                Source={x:Reference MainContent}}"></behaviors:InvokeCommandAction>
                            </behaviors:EventHandlerBehavior>
                    </ListView.Behaviors>
                    <ListView.ItemTemplate>
                        <DataTemplate>
                            <ViewCell>
                                <StackLayout HeightRequest="50">
                                    <Label Text="{Binding}" Margin="20,0,0,0" 
                                           VerticalOptions="CenterAndExpand"></Label>
                                </StackLayout>
                            </ViewCell>
                        </DataTemplate>
                    </ListView.ItemTemplate>
                </ListView>
            </StackLayout>
        </ContentPage.Content>
    </views:MvxContentPage>

    现在是时候为所有三个平台启动应用程序并观察最终结果!现在,该应用程序显示三个平台的正确汉堡菜单,它具有适当的边距和样式。

    小结

    在本文中,我向您展示了如何通过Xamarin.Forms和MvvmCross Framework为iOS,Android和UWP创建基本的汉堡菜单布局。我不是专业设计师,因此您可能需要为自己的应用程序微调样式。我希望您可以按照这些步骤创建一个干净,优雅的MVVM架构的汉堡菜单布局。另外,我希望你能从我的演示中获得数据绑定基础知识。请记住,实现相同目标可能有多种方法,而我的实施并不是最好的方法。实际上,我认为为每个项目添加一个图标会更好!如果您找到更好的解决方案,请留下评论并在下面进行讨论。

    你可以在我的GitHub上找到repo:MvxFormsMasterDetailDemoHappy Coding!

     

  • 相关阅读:
    Linux常用命令英文全称与中文解释Linux系统
    最流行的android组件大全
    Android eclipse中程序调试
    Oracle----Key Word
    Oracle----date
    ANDROID代码实现APK文件的安装与卸载
    listview加载性能优化
    【369】列表/字典的分拆, unpacking
    【368】相关术语说明
    【367】通过 python 实现 SVM 硬边界 算法
  • 原文地址:https://www.cnblogs.com/yanxiaodi/p/Implementing-MasterDetail-layout-in-XamarinForms-by-MvvmCross.html
Copyright © 2011-2022 走看看