Xamarin Forms的Prism概述:第一部分
尽管我知道它不可能是每个项目都适用的技术,但我还是Xamarin Forms的超级粉丝。如果你看完我的博客,你可能现在也是一名Xamarin粉,我是一个Windows开发者,Xamarin Forms允许我使用基本上都是我现有的技能(XAML、绑定、MVVM,等等)来创建应用程序,也包括其他流行的像iOS和Android平台。此外,它给了我利用平台的具体功能的机会(如本地Xamarin),同时保持与操作系统的外观和感觉相一致的用户体验。
我最近用Xamarin Forms为我简单的Qwertee Shirts app创造一个Android移植,优势很明显:我能够重用我已经写好的UWP版本的大多数后端代码和我的XAML知识,最后,我得到了一个从UI角度完全接受的应用程序,由谷歌创建的新Material Design,所以它不像基于Web技术的跨平台应用经常发生的看起来“外星人”一样。
但是,我不只是一个Windows开发者也是一个MVVM爱好者,我在多次写这个话题,涵盖多个平台和框架。如果MVVM模式对你来说还是新事物,我建议你从这篇帖子开始看,然后继续看完本系列的其余部分。当我决定恢复使用Xamarin Forms时我做的第一件事是转向我的应用程序端口。我正在寻找我的MVVM知识重用以开发项目的最佳方式,像往常一样,选择是艰难的。在这种情况下,更为复杂的是Xamarin Forms,相比其他像WPF或UWP的XAML技术,是相当新的,所以很难找到一个完全满足我的选择。
别误会,如果你还记得我写UWP应用关于Template10的帖子,你会知道我是MVVM Light所提供的灵活性的一个超级粉丝,Laurent Bugnion很好的介绍了在本身不支持的平台的典型MVVM概念(如绑定和命令),像Android和iOS。然而,Xamarin Forms与标准Xamarin相比有一点不同:它已经提供了我们需要的概念来使用MVVM模式,如绑定、数据背景、依赖属性、行为等。在这种情况下,MVVM Light仍然是一个极好的选择但你仍然要推倒重来解决许多你必须处理的常见的场景,当你开发一个XAML应用程序,如处理导航,进入一个ViewModel导航事件,或通过一页与另一页之间的参数。
就在我开始移植之前,我看到了Brian Lagunas的推特, Prism项目背后的MVP之一,宣布专为Xamarin Forms创建的Prism的新版本。来理清你的头脑,Prism是一个MVVM框架,最初是由微软的模式与实践部门创造的,后来变成了社区运营的一个开源项目。Prism一直是基于XAML的应用实现MVVM模式的一个很好的选择,但有时你可能会面临使项目只是遵循命名约定和规则而过于复杂的风险(像是有一个引导程序要求对它进行初始化,尽管基于XAML应用程序已经启动称为App的类)。
完成移植后,我发现自己对Xamarin Forms的Prism方法感到很满意,所以我决定与你分享我的经验,希望这会让你在开始一个新的Xamarin Forms项目时更快地启动和运行。
创建第一个项目
创建一个基于Prism的Xamarin Forms项目最简单的方法是使用自己的Visual Studio扩展,你可以从Visual Studio Gallery下载。安装完毕后,你将在Visual Studio中找到一个新的称为“Prism”的部分,每个支持的技术都有不同的模板。我们感兴趣的模板被称为“Prism Unity App (Forms)”:
其实,这个模板有一个优于标准Xamarin Forms模板的优点。正如你可以从下面的图片看到的,它允许你当你创建你的项目时选择你想要作为目标的平台,而默认的Xamarin Forms模板为每个支持的平台自动创建一个项目(Android、iOS、Windows Phone 8.1、Windows 8.1、UWP),即使你对它们任何一个都没有兴趣。
当你点击Create 项目,你将得到一个标准的Xamarin Forms解决方案:一个便携式类库和一个你选择的每个平台的特定项目。此外,便携式类库已经包含:
- Views文件夹,建立你的页面。包括一个称为MainPage.xaml的默认的模板。
- ViewModels文件夹,存放你的ViewModels。包括一个称为MainPageViewModel.cs的默认的模板。
- 一个App类已经配置初始化Prism基础构造。
你的默认项目看起来将是这样:
为了演示Xamarin Forms的Prism,我要创造TrackSeries简单的客户端,我的好朋友和同事Adrian Fernandez Garcia和Carlos Jimenez Aliaga创造的电视节目网站。
让我们从头开始,看看哪些引用已被模板自动添加到项目中去了:
你可以看到,除了标准Xamarin Forms NuGet包,Prism还需要两个套包:Core(这在每个平台都是常见的)和Forms(包含Xamarin Forms的特定的助手和服务)。默认情况下,标准模板利用Unity为依赖注入容器,所以你会发现一堆其他套包像Unity、Prism.Unity.Forms和CommonServiceLocator。然而,如果你不喜欢Unity,Xamarin Forms的Prism会提供了一些额外的套包,整合了其他流行的依赖注入容器,如Ninject或Autofac。
应用程序类
相比老的Prism版本,其中一个最大的变化是引导程序概念的去除,这是一个专门的项目类,负责初始化所有Prism基础构造。Xamarin Forms(同其他XAML技术一样)已经有一个初始化类:App,包含在便携式类库里,所以团队决定利用它而不是要求开发人员创建一个新的。默认情况下,这个类是继承自应用程序类。为了正确地支持Prism,我们需要改变它并让App类从PrismApplication继承:
在 App.xaml 文件中,添加新的命名空间标识符 Prism.Unity并用PrismApplication 节点替换Application 节点。
<?xml version="1.0" encoding="utf-8" ?> <prism:PrismApplication xmlns="//xamarin.com/schemas/2014/forms" xmlns:x="//schemas.microsoft.com/winfx/2009/xaml" xmlns:prism="clr-namespace:Prism.Unity;assembly=Prism.Unity.Forms" x:Class="InfoSeries.App"> </prism:PrismApplication>
在App.xaml.cs文件中,我们需要改变默认从Application到PrismApplication继承。
public partial class App : PrismApplication { public App(IPlatformInitializer initializer = null) : base(initializer) { } protected override void OnInitialized() { InitializeComponent(); NavigationService.NavigateAsync("MainPage"); } protected override void RegisterTypes() { Container.RegisterTypeForNavigation<MainPage>(); } }
此外,App类有三个鲜明的特点:
- 具有基本构造函数,以一个IPlatformInitializer对象作为参数。
- 有一个称为OnInitialized()的方法,我们初始化Forms基础构造(通过调用InitializeComponent()方法),并且我们触发导航到该应用程序的主页(我们后面将看到导航如何工作的详细内容)。
- 有一个称为RegisterTypes()的方法,就是我们登记的依赖注入容器(在这种情况下,Unity框架)的每一页和我们的应用程序所需的所有服务。
默认情况下,IPlatformInitializer参数为null,它可以在你需要注册依赖容器一些只在特定平台的项目存在的特定类别时利用。你会发现,事实上,每个平台的特定项目都有自己的自定义初始化类(AndroidInitializer 、UWP的UwpInitializer,等等),但是,默认的有RegisterTypes()方法空的实现。下面是UWP项目的MainPage.xaml.cs:
public sealed partial class MainPage { public MainPage() { this.InitializeComponent(); LoadApplication(new DeepNavigation.App(new UwpInitializer())); } } public class UwpInitializer : IPlatformInitializer { public void RegisterTypes(IUnityContainer container) { } }
连接Views和ViewModels
你应该已经知道,如果你有一些MVVM的经验,使该模式运行的关键是将ViewModel与它自己的View连接。Xamarin Forms应用程序与Windows应用程序唯一的不同就是定义背景的属性称为BindingContext而不是DataContext。Prism使用一种简单的命名约定来自动分配ViewModel到它的View:
- XAML页面应该被存储在一个被称为Views的项目文件夹中
- ViewModel应存放在一个被称为ViewModels的项目文件夹中,它需要与页面相同的名称加上后缀ViewModel(例如,ViewModel连接到MainPage.xaml将被称为MainPageViewModel)。
正如你所看到的,这是Prism模板为我们创建的确切的基础构造。我们添加到我们的应用程序的每一个页面都需要在容器中注册,以便我们能够正确地处理导航。为了注册,我们可以利用App类的RegisterTypes()方法和使用一种由Container提供的称为RegisterTypeForNavigation< T >的方法,其中T是网页的类型。在起始模板,我们只有一个称为MainPage的网页,所以这是唯一一个在应用程序启动时自动注册的页面。Prism和其他MVVM框架之间有一个最大的差异。使用其他的工具,你只能在容器中注册ViewModels和最终与他们有关联的所有服务。相反地,使用Prism你只需注册页面的类型:根据Prism自动在容器注册,ViewModel也连接到View。你可以看到在示例代码中,我们已经注册了MainPage类而不是MainPageViewModel。
如果你不是命名方法的粉丝,你不必使用它:事实上,RegisterTypeForNavigation()方法有另一个变种,其签名是RegisterTypeForNavigation< T, Y >(),其中T是页面的类型,Y是我们要设置为BindingContext的ViewModel的类型。所以,例如,如果你想把你的MainPage连接到一个称为MyCustomViewModel的ViewModel,使用下面的代码就足以注册:
protected override void RegisterTypes() { Container.RegisterTypeForNavigation<MainPage, MyCustomViewModel>(); }
在OnInitialized()方法中你可以预览默认的导航是如何工作的:每次你调用RegisterTypeForNavigation< T >方法,Prism注册NavigationService参考页面作为关键使用,具有相同类型名称的字符串。由于我们的页面的类型是MainPage,我们需要通过字符串“MainPage”作为NavigateAsync()方法参数触发导航到该页面。如果我们要重写此行为,我们可以通过作为RegisterTypeForNavigation< T >()参数自定义字符串,用于后续的导航,如下面的示例中,我们已经用“MyCustomPage”页面取代了关键的“MainPage”。
public partial class App : PrismApplication { public App(IPlatformInitializer initializer = null) : base(initializer) { } protected override void OnInitialized() { InitializeComponent(); NavigationService.NavigateAsync("MyCustomPage"); } protected override void RegisterTypes() { Container.RegisterTypeForNavigation<MainPage>("MyCustomPage"); } }
然而,在下一篇文章中,我们将看到更多关于如何以更高级的方式来处理导航的详细信息。
ViewModel
在Xamarin Forms的Prism中我最欣赏的特点是,它不需要我们在XAML页面做任何变化就可以支持它(例如,其他一些MVVM框架需要你用定制的一个去改变ContentPage类型)。你只会发现,在MainPage.xaml文件中有一个Prism特性,就像ContentPage项目属性,称为ViewModelLocator.AutowireViewModel:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="//xamarin.com/schemas/2014/forms" xmlns:x="//schemas.microsoft.com/winfx/2009/xaml" xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms" prism:ViewModelLocator.AutowireViewModel="True" x:Class="InfoSeries.Views.MainPage" Title="MainPage"> <StackLayout HorizontalOptions="Center" VerticalOptions="Center"> <Label Text="{Binding Title}" /> </StackLayout> </ContentPage>
该属性负责连接View与ViewModel:当它设置为true,ViewModel将自动设置为View 的BindingContext,如果我们遵循先前描述的命名惯例。然而,在“Prism 6.2”中介绍的一个变化是,这个属性是不再需要的,除非你想通过设置它为false明确禁用命名约定。标准的模板将它添加到一个更完整的示例中,但你仍然可以安全地删除它。
每个MVVM框架所提供的一个关键的功能是一个类,给我们的ViewModels提供快速访问到最常用的功能,就像INotifyPropertyChanged接口的实现。Prism也不例外,它提供了一个称为BindableBase的类,我们的ViewModels可以继承:
public class MainPageViewModel : BindableBase { private string _title; public string Title { get { return _title; } set { SetProperty(ref _title, value); } } public MainPageViewModel() { } }
多亏这个类,每当我们需要创建一个属性来实现INotifyPropertyChanged接口(这样可以通过结合渠道传播改变),我们可以简单的使用属性调节器中的SetProperty()方法。此方法将用于存储值,同时,发送一个通知到所有与此属性绑定的控件,告知它的值已更改,因此需要更新它们的布局。
通过模板创建的示例应用程序正是这样:它创建一个名为Title的属性,通过结合到XAML页面的Label控件连接。当我们改变属性的值时,我们会看到用户界面的实时更新。说实话,这个示例应用程序也展示了一些别的东西:它用一种称为OnNavigatedTo()的方法设置了Title属性的值,并且解析了一些参数。我们将在下一篇文章中看到更多这种方法如何运作的细节。
在下一篇文章中
在这篇文章中,我们只是触及表面,展现了Prism创建的Xamarin Forms应用程序的基本概念。在接下来的文章中,我们会看到一些更先进的概念,像在ViewModel处理导航或在依赖容器注册附加服务。
本文翻译自:
最新活动推荐:年中大促|在线订购全场7折起!点击了解详情>>