使用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!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM