注:本文是英文寫的,偷懶自動翻譯過來了,原文地址: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文件夾,然后選擇Add
- New Item
。Content 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:TypeArguments
值viewModels:MasterDetailViewModel
。不要忘記通過添加以下代碼導入viewModels命名空間:xmlns:viewModels="clr-namespace:MvxFormsMasterDetailDemo.Core.ViewModels;assembly=MvxFormsMasterDetailDemo.Core"
。
打開MasterDetailPage.xaml.cs
文件,將其基類替換ContentPage
為MvxMasterDetailPage<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
屬性。有一些非常重要的屬性MvxMasterDetailPagePresentation
。Position
是一個枚舉值,用於指示頁面的類型,在此處設置為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用來
存儲一些菜單項的屬性。為簡單起見,只有兩個字符串:Contacts
和Todo
。我們還需要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.xaml
和TodoPage.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.xaml
和TodoPage.xaml.cs做同樣的更改
。不要忘記更新Label控件的Text以顯示頁面名稱。
實現菜單功能
現在,我們有我們需要顯示所有的頁面:根頁叫MasterDetailPage
,一個MasterPage叫做MenuPage
和兩個DetailPages叫做ContactsPage
和TodoPage
。接下來,我們需要使菜單正常工作。
顯示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的布局,只需設置MasterBehavior
MasterDetailPage 的屬性即可。它是一個枚舉值,用於確定詳細信息頁面在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
,因為我們知道MenuItemList
is中的對象是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
,比如Button
,MenuItem
,TextCell
和一些繼承自它們的類。並且,SearchBar
支持SearchCommand,
實際上也是一種ICommand
類型的屬性。ListView的
也是RefreshCommand
屬性ICommand
接口的實例。
對於那些不直接支持ICommand的控件,Xamarin.Forms提供了一個TapGestureRecognizer來
支持Command
綁定。有關詳細信息,請閱讀以下文章:添加點按手勢識別器。請記住,雖然GestureRecognizer
支持多種手勢,如pinch
,pan
和swipe
,但只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
替換默認的TextCell
。ViewCell
為我們提供了更多自定義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
。
ShowDetailPageAsync
在switch
段之后的方法中添加一些代碼(這段代碼來自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
此外,您可能認為我們可以使用ItemSelected
或ItemTapped
事件。當然,我們可以!但不幸的是,這些事件沒有實現ICommand
接口,所以我們不能直接使用數據綁定。要使用ICommand
綁定,我們需要使用a Behavior
將事件轉換為命令,如下所述:可重用的EventToCommandBehavior。
您可能不熟悉行為。行為來自Blend SDK,它是XAML世界中非常有用的庫。可以將這些行為附加到某些控件並偵聽某些事件,然后在ViewModel中調用某些命令。這是為那些未設計為與Command交互的控件添加Command模式支持的好方法。因此,我們可以優雅地使用MVVM模式,而不是在代碼隱藏文件中使用事件處理程序。
您可以按照官方文檔中的說明創建您的EventToCommandBehavior
,但我們可以利用第三方庫快速完成:Behaviors.Xamarin.Forms.Netstandard。它不是官方項目,但易於使用。您可以通過搜索Behaviors.Xamarin.Forms
NuGet包管理器將其安裝到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。請注意數據綁定語法。我們需要指定綁定Source
和Path
綁定。如果你只是使用{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
屬性,表示當前平台。有一些不同的值,以確定不同的平台:iOS
,Android
,UWP
和macOS
。因此,對Android和iOS來說,我們可以通過設置其屬性來創建一個包含Label
控件StackLayout並設置其其
以顯示應用名稱。但對於UWP來說,它是看不見的。這意味着添加代碼不會對UWP進行任何更改。IsVisible屬性
運行適用於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!