註:本文是英文寫的,偷懶自動翻譯過來了,原文地址:Implementing MasterDetail layout in Xamarin.Forms by MvvmCross 歡迎大家關註我的公眾號:程式員在紐西蘭,瞭解美麗的紐西蘭和碼農們的生活 閱讀本文大概需要20分鐘。本文目錄: 前言 通過Mv ...
註:本文是英文寫的,偷懶自動翻譯過來了,原文地址: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"