天天看點

使用Xamarin與MvvmCross完成一個跨平台App

我們所做的第一個Model-View-ViewModel(MVVM)程式需要為一個餐館實作一個跨平台的小費電腦。

他的線框圖大概是下面這個樣子:

使用Xamarin與MvvmCross完成一個跨平台App

App線框圖

本片文章大概會涉及到以下幾個方面:

MvvmCorss應用程式的總體結構。 建構一個MvvmCross程式所需要的基本代碼。 如何在Xamarin.iOS、Xamarin.Android、Windows 通用應用程式中使用MvvmCross以及資料綁定。

本文隻關注與MvvmCross,Xamarin相關的問題不會詳細的說明。

一個MvvmCross的App通常由以下幾個部分組成:

一個共享的核心(Core)跨平台類庫(Protable Class Library,PCL)。包含view model、service、converters等。 各 個平台的的UI工程。

一般來說我們從這個跨平台的Core庫開始寫。

本例使用Visual Studio 2013,Xamarin 3.11.386。iOS部分使用虛拟機、作業系統為10.10.3 xcode6。

首先在VS中建立一個PCL項目。名稱為TipCalc.Core,解決方案名稱為TipCalc(小費電腦):

使用Xamarin與MvvmCross完成一個跨平台App

建立項目

在建立PCL項目時,VS會詢問PCL的目标平台,請按照下面圖檔設定:

使用Xamarin與MvvmCross完成一個跨平台App

PCL目标平台

確定PCL的Profile為Profile259,實在不想選可以通過編輯PCL工程的csproj檔案将

中的值改為Profile259。

如果你的VS報錯請參考 http://danrigby.com/2014/04/10/windowsphone81-pcl-xamarin-fix/

以及yzf的部落格: http://www.cnblogs.com/yaozhenfa/p/4709952.html

關于Profile259,Profile259包括了大多數.net程式集,也可以通過Nuget擷取第三方的庫,通常跨平台的類庫都是使用Profile259生成的。

在 [工具] - [Nuget 程式包管理器] - [程式包管理器控制台]  (Package Manager Console)中輸入

這時Nuget會自動幫我們把MvvmCross引入到我們的工程中。

然後删掉自動生成的Class1.cs。

當然你也可以在項目右鍵,使用Nuget程式包管理器引入MvvmCross。不過根據我的經驗,Nuget管理器通常會比較卡。推薦用控制台。

在項目中增加檔案夾,名為Services。在小型App中我們可以把App的業務邏輯放在這裡。

在檔案夾中增加一個接口。這個接口用來抽象計算小費的邏輯。

然後我們來實作它

到此為止我們App的業務邏輯已經實作了。

在添加ViewModel前我們先梳理下我們這個頁面需要完成的任務

用途: 使用Calculation服務計算小費。 需要使用者輸入: 賬單的總價(SubTotal)。 我們想要留下小費的百分比 (Generosity)。 輸出: 我們需要留下多少小費。

在MvvmCross中,所有的ViewModel都需要繼承自MvxViewModel。

在項目中建立ViewModels檔案夾,專門用來放ViewModel。在其中建立新的ViewModel.

如果你之前在WPF、Sliverlight等平台中接觸過MVVM設計模式應該對以上的代碼并不陌生。

這個ViewModel中有3個被擴充過的屬性SubTotal、Generosity、Tip。

當他們被修改的時候會調用RaisePropertyChanged函數來通知其他的對象他們的屬性被修改過了。

且當SubTotal和Generosity被修改時會重新計算小費。

在寫好Calculation Service和TipViewModel之後,我們現在來添加App的啟動配置代碼,對于App類來說:

他通常會待在在PCL項目的根目錄下。 他繼承自MvxApplication。 一般來說他的名字就叫App。 他的主要功能是: 為IoC容器注冊接口以及相應的實作。以後我會專門寫一篇關于MvvmCross的IoC容器的文章來介紹。 設定App啟動後第一個界面對應的的ViewModel。 為整個App提供ViewModel的定位器(Locator)。定位器作用是通過ViewModel的Type以及以下參數來生成對應的ViewModel。通常情況下我們用預設的就行了。

對于我們的小費電腦來說,App的代碼很簡單:

使用Xamarin與MvvmCross完成一個跨平台App

6.完成!

到此我們完成了Core工程的全部代碼。來看看我們具體都做了什麼:

用Profile 259建立了一個PCL項目。 用Nuget向項目中添加了MvvmCross的程式集。 添加了ICalculate接口和他的實作。 添加了TipViewModel。 繼承自MvxViewModel。 向架構請求了ICalculate服務。 添加了一些會調用RaisePropertyChanged函數的公共屬性。 添加了App啟動配置。 繼承自MvxApplication。 為ICalculate接口注冊了他的具體實作。 注冊了應用程式的啟動界面。

差不多每個使用MvvmCross的App都會有以上步驟。

接下來我們來看看每個平台該做些什麼。

Android部分y-z-f已經有寫過相關的文章了:

http://www.cnblogs.com/yaozhenfa/p/4709952.html

在解決方案中建立一個空的Android項目([Android] — [Blank App]),命名為TipCalc.UI.Droid。

與Core一樣使用Package Manager Console安裝MvvmCross。記得切換Package Manager Console的對應項目。

因為需要在Android的axml檔案中使用MvvmCross的相關指令,需要添加一個名為MvxBind.Xml檔案到Resources/Values檔案夾中。内容如下

使用Xamarin與MvvmCross完成一個跨平台App

我們需要一個Setup類來控制Android App的啟動行為,在Android根目錄下添加一個Setup類,内容如下:

使用Xamarin與MvvmCross完成一個跨平台App

本文中使用預設的啟動行為,是以簡簡單單的繼承MvxAndroidSetup就行了。

Android的View分為2個部分:Layout與Activity。資料綁定可以直接寫在布局檔案中。

在Resource\layout中添加View_Tip.axml檔案,内容如下:

使用Xamarin與MvvmCross完成一個跨平台App

Android的資料綁定形如:

這句話相當于WPF中的:

前一個屬性表示需要綁定控件的哪個屬性,後一個屬性表示需要将前面指定的屬性綁定到ViewModel的哪個屬性。

MvvmCross提供了大部分控件屬性的綁定模式,對于沒有預設實作的屬性我們可以自定義綁定。

因為在Android中AXML檔案需要經由Activity(Fragment)來呈現,而且也可以順便在Activity寫一些AXML檔案裡面無法寫的背景代碼,是以MVVM中的View在Android指的是Activity。

本處于原文有一定差異,原文中MvvmCross官方将Activity檔案命名為xxxView,容易和Android中的View混淆,是以在這裡我将其命名為xxxViewActivity。

在項目中建立Views檔案夾,并添加一個TipViewActivity類,内容如下:

使用Xamarin與MvvmCross完成一個跨平台App
此處與原文有差異,原文是利用new關鍵字覆寫了父類中的ViewModel并沒有使用泛型。這裡推薦用使用泛型類。
使用Xamarin與MvvmCross完成一個跨平台App

Alt text

接下來看看如何實作iOS App。

本文與原文有一定差異,原文中是用Xib的方式建立界面,在本文中為了友善直接使用代碼建立界面。 MvvmCross不推薦使用Storyboard建立iOS界面,因為Storyboard包含有一定的邏輯成分,如導航的邏輯,況且在iOS程式設計中Storyboard、Xib、純代碼三種建立界面的方式也一直在争論。我的推薦是利用純代碼建立界面,至于原因我以後會詳細說明。MvvmCross也可以使用Storyboard的,如何使用我也會在後續的文章中說明。

在解決方案中建立一個空的iOS項目([iOS]—[Universal]—[Blank App(iOS)]),名稱為TipCalc.UI.Touch。

和Android一樣,用Package Manager Console安裝就行。

操作也和Android一樣,不過構造函數有些許不同。

使用Xamarin與MvvmCross完成一個跨平台App

首先我們需要将AppDelegate的基類改為MvxApplicationDelegate。

修改FinishedLaunching函數,這個函數是在App啟動初始化完成後被調用的。

使用Xamarin與MvvmCross完成一個跨平台App

修改後的AppDelegate.cs檔案如下:

使用Xamarin與MvvmCross完成一個跨平台App
因為iOS原本為MVC模式,UIView隻是純粹的界面,并不能添加邏輯代碼,是以對于MvvmCross來說,iOS的View應該是ViewController。Xib與Storyboard的描述檔案雖然是Xml但是可讀性很差,蘋果也不推薦修改Xml,是以資料綁定等代碼需要寫在ViewController裡面。 原文中官方對ViewController的命名是直接命名成View的,但是我覺得會和UIView混淆,是以對MVVM中View在iOS中的命名寫成xxxxViewController。

在項目中建立Views檔案夾,并在其中添加一個類,名稱為TipCalcViewController:

因為上面所說的原因,iOS的綁定需要在ViewController内使用代碼進行綁定,雖然比Android和Windows平台複雜,但是總的來說還是比較簡單的。

等同于

因為大部分控件都有一個預設的綁定屬性,是以在大部分情況下可以省略指定屬性的步驟。

目标屬性指定時也可以直接用字元串,實際上利用表達式樹的形式也是轉換為字元串的,這樣做的目的是為了在重命名時能夠自動修改所有的綁定,避免重命名時少修改了一個字元串而導緻的錯誤。

來看看iOS的效果吧:

使用Xamarin與MvvmCross完成一個跨平台App

前面我們已經完成了Android和iOS部分,接下來我們來看看MvvmCross如何在Windows通用程式使用。

本例以2013的Win8通用程式作為例子。Win10我還沒看。

在VS中建立一個[應用商店]—[空白應用程式(通用應用程式)](Blank App Universal Apps),名稱為TipCalc.UI.Win。

一個通用應用程式包括了3個部分:

Shared庫項目,這是一個Windows項目和WindowsPhone項目共用的部分。通常我們會把這2個平台的可以共用業務邏輯放在這,不過因為MvvmCross已經将業務邏輯移到Core庫中,以供多個平台使用,是以在這個Share庫裡面不會有過多的代碼。 Windows平台UI項目。運作Win8和Win10的裝置将會使用這個項目。 WindowsPhone平台UI項目。運作WindowsPhone的裝置将會使用這個項目。

和Android、iOS一樣,分别對2個平台的UI項目使用Package Manager Console

然後在分别引用Core庫。并删掉各自的MainPage.xaml。

在Shared項目的根目錄中建立一個Setup類,正如我們前面說的一樣,每一個平台都需要對應的Setup類來控制程式的啟動行為。對于Windows和WindowsPhone 我們可以共用一個Setup類。

使用Xamarin與MvvmCross完成一個跨平台App

Setup本質上是傳回一些用來控制程式運作的對象,當我們需要在Windows和WindowsPhone中實作不同的效果,可以用條件編譯等方式讓Setup傳回不同的對象來達到控制程式不同運作效果的目的。

修改App.xaml.cs檔案的OnLaunch回調函數:

先删掉這幾行:

然後在這幾行的位置輸入下面這段代碼:

這部分需要分别對Windows與WindowsPhone項目進行操作,但是操作的過程是一模一樣的,除了WindowsPhone的布局有點不一樣。這裡我們隻說如何在Windows項目操作。

在TipCalc.UI.Win.Windows項目中建立Views檔案夾。在其中添加一個基本頁(Basic Page),名為TipView.xaml。

建立基本頁的過程中VS會問我們是不是需要添加一些輔助類,選是,這時VS會自動向項目中添加一些輔助類,不過在本文中我們用不着他們,不用管他們。

在TipView.cs檔案中将TipView的基類改為MvxWindowsPage:

修改為:

并修改OnNavigationTo和OnNavigationFrom回調函數,讓其調用基類對應的函數。這樣做的目的是為了讓MvvmCross知道頁面的狀态,并執行相應操作,如利用IoC填充ViewModel屬性。

使用Xamarin與MvvmCross完成一個跨平台App

與iOS和Android不同的是我們需要用new關鍵字手動設定ViewModel。

這裡與原文有點差異,原文3個平台都是用這個方法設定ViewModel的,但是因為可以利用泛型來設定ViewModel,是以我們沒有采用這種方法。通用應用程式與iOS、Android不同的是,他的View的基類在Xaml中定義了一次,而Xaml不能使用泛型(也可能是我不知道怎麼設定),是以隻能采用這種方式。 為什麼一定要設定好View的ViewModel的類型,而不是使用一個公用的基類呢?因為MvvmCross是通過反射View的ViewModel屬性來确定View和ViewModel的對應關系的,如果你将2個View裡面的ViewModel的類型設定為一樣,MvvmCross啟動的時候就會報錯,因為他不知道ViewModel該如何對應這2個View。因為MvvmCross的導航系統是基于ViewModel的,是以View和ViewModel必須是一一對應的。 當然也有方法讓不同的View對應相同的ViewModel,因為涉及到導航系統,是以在這裡不展開講,以後會有專門的文章來介紹的。

修改TipView.cs增加如下代碼:

這樣後我們的TipView.cs檔案大概是這樣子的:(我删掉了注釋)

首先我們需要修改Page的基類為MvxWindowsPage,将:

手動拖入控件,或者自己寫Xaml,讓Page看起來像這個樣子:

使用Xamarin與MvvmCross完成一個跨平台App

Windows平台界面

Xaml代碼如下:

使用Xamarin與MvvmCross完成一個跨平台App

⑥WindowsPhone項目與Windows的差別

首先是布局上的差別,很明顯FontSize = 40在WP上有點大,還有StackPaanel的Margin也有點大,改到比較合适的值就行了。

然後是WindowsPhone的導航行為和其他平台有一定的差異,系統會緩存View來複用,是以我們需要在邏輯上建立一個新View時重置他的ViewModel。

我也沒怎麼研究過WindowsPhone,是以不了解這個複用機制。如果我說的有問題請在評論中指出。

在修改TipView.cs時需要将OnNavigationTo函數修改成下面這樣:

讓我們看看2個平台的運作效果:

使用Xamarin與MvvmCross完成一個跨平台App

Windows平台運作效果

使用Xamarin與MvvmCross完成一個跨平台App