注:本文是英文写的,偷懒自动翻译过来了,原文地址: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:MvxFormsMasterDetailDemo。Happy Coding!

使用MvvmCross框架实现Xamarin.Forms的汉堡菜单布局的更多相关文章

  1. 老司机学新平台 - Xamarin Forms开发框架二探 (Prism vs MvvmCross)

    在上一篇Xamarin开发环境及开发框架初探中,曾简单提到MvvmCross这个Xamarin下的开发框架.最近又评估了一些别的,发现老牌Mvvm框架Prism现在也支持Xamarin Forms了, ...

  2. Xamarin.Forms 入门

    介绍 Xamarin.Forms是一个开源UI框架,Xamarin.Forms允许开发人员从单个共享代码库构建Android,iOS和Windows应用程序. Xamarin.Forms允许开发人员使 ...

  3. 搞懂Xamarin.Forms布局,看这篇应该就够了吧

    Xamarin.Forms 布局介绍 什么是布局?可以简单的理解为,我们通过将布局元素有效的组织起来,让屏幕变成我们想要的样子! 我们通过画图的方式来描述一下Xamarin.Forms的布局. 小节锚 ...

  4. 老司机学新平台 - Xamarin Forms开发框架之MvvmCross插件精选

    在前两篇老司机学Xamarin系列中,简单介绍了Xamarin开发环境的搭建以及Prism和MvvmCross这两个开发框架.不同的框架,往往不仅仅使用不同的架构风格,同时社区活跃度不同,各种功能模块 ...

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

    所谓 UWP 样式的汉堡菜单,我曾在"张高兴的 UWP 开发笔记:汉堡菜单进阶"里说过,也就是使用 Segoe MDL2 Assets 字体作为左侧 Icon,并且左侧使用填充颜色 ...

  6. Xamarin.Forms入门学习路线

    Xamarin 介绍 Xamarin是一套跨平台解决方案,目的是使用C#语言创造原生的iOS,Android,Mac和Windows应用. Xamarin的三个优势: Xamarin App拥有原生A ...

  7. Xamarin.Forms 开发资源集合(复制)

    复制:https://www.cnblogs.com/mschen/p/10199997.html 收集整理了下 Xamarin.Forms 的学习参考资料,分享给大家,稍后会不断补充: UI样式 S ...

  8. Xamarin.Forms 开发资源集合

    收集整理了下 Xamarin.Forms 的学习参考资料,分享给大家,稍后会不断补充: UI样式 Snppts: Xamarin Forms UI Snippets. Prebuilt Templat ...

  9. MvvmCross框架在XamarinForms中的使用入门

    做XamarinForms快一年了,最近趁着项目不是很紧,有点空闲的时间,研究了一下MvvmCross这个框架,感觉挺高大上的.一边研究一下写点入门的东西吧,大部分的东西github都有. 1添加Pa ...

随机推荐

  1. 【Spring源码解析】—— 结合SpringMVC过程理解IOC容器初始化

    关于IOC容器的初始化,结合之前SpringMVC的demo,对其过程进行一个相对详细的梳理,主要分为几个部分: 一.IOC的初始化过程,结合代码和debug过程重点说明 1. 为什么要debug? ...

  2. VBA解析Json(转)

    Sub bluejson() 'ok Dim aa Set x = CreateObject("ScriptControl"): x.Language = "JScrip ...

  3. 自适应Web主页

    HTML <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF- ...

  4. LR 11录制IE起不来

    注:LR 11一般使用的是IE8或IE9 1.在录制脚本时Start Recoding中,默认如下,这样有可能IE打不开,需要更改路径,到对应的IE路径再尝试. 2.降低IE版本到IE8或者9 3.I ...

  5. CSS3网页动画

    CSS3网页动画 概要:CSS3变形是一些效果的集合 如:平移.旋转.缩放.倾斜效果 每个效果都可以称为变形(transform)他们可以分别操控元素发生平移.旋转.缩放.倾斜等变化. 网页中能够实现 ...

  6. Yii2.0 解决“the requested URL was not found on this server”问题

    在你下了 Yii 框架,配置完路由 urlManager 后,路由访问页面会报错“the requested URL was not found on this server”,url类似于这种“ht ...

  7. luogu||P1776||宝物筛选||多重背包||dp||二进制优化

    题目描述 终于,破解了千年的难题.小FF找到了王室的宝物室,里面堆满了无数价值连城的宝物……这下小FF可发财了,嘎嘎.但是这里的宝物实在是太多了,小FF的采集车似乎装不下那么多宝物.看来小FF只能含泪 ...

  8. python实现bt种子 torrent转magnet

    Python实现bt转磁链  参考前人资料主要两种方式 1,利用python的bencode模块 2,安装libtorrent模块 尝试过两种方法特记录 环境:Windows系统  python 3 ...

  9. idea实现热部署并且开启自动编译

    [注]本文转自https://blog.csdn.net/z15732621582/article/details/79439359博文,如有冒犯,请联系博主: 问题描述: 最近在调试代码并进行本地测 ...

  10. Vue+Webpack构建去哪儿APP_一.开发前准备

    一.开发前准备 1.node环境搭建 去node.js官网下载长期支持版本的node,采用全局安装,安装方式自行百度 网址:https://nodejs.org/zh-cn/ 安装后在cmd命令行运行 ...