【xamarin + MvvmCross 从零开始】五、MvvmCross 详解 (3)
前言
上一篇我们讲了MvvmCross的数据绑定,这次我们讲一下 ViewModel对象。
ViewModel 对象详解
ViewModel对象是Mvvm框架的核心对象,Mvvm模型中起到连接Model与View的作用。ViewModel可以理解为对Model的一个包装,通过对Model进行包装,隐藏与View无关的内容,以方便View进行数据呈现。
在MvvmCross框架内,ViewModel必须实现IMvxViewModel接口,MvvmCross对IMvxViewModel的默认实现 MvxViewModel。
ViewModel的生命周期
构造
MvvmCross中ViewModel的构造一般有以下方式:
- 通过导航到指定的ViewModel,MvvmCross将自动创建指定的ViewModel。
public abstract class MvxNavigatingObject : MvxNotifyPropertyChanged { protected MvxNavigatingObject(); protected IMvxViewDispatcher ViewDispatcher { get; } protected bool ChangePresentation(MvxPresentationHint hint); protected bool Close(IMvxViewModel viewModel); protected bool ShowViewModel(Type viewModelType, object parameterValuesObject, IMvxBundle presentationBundle = null, MvxRequestedBy requestedBy = null); protected bool ShowViewModel(Type viewModelType, IDictionary<string, string> parameterValues, IMvxBundle presentationBundle = null, MvxRequestedBy requestedBy = null); protected bool ShowViewModel(Type viewModelType, IMvxBundle parameterBundle = null, IMvxBundle presentationBundle = null, MvxRequestedBy requestedBy = null); protected bool ShowViewModel<TViewModel>(object parameterValuesObject, IMvxBundle presentationBundle = null, MvxRequestedBy requestedBy = null) where TViewModel : IMvxViewModel; protected bool ShowViewModel<TViewModel>(IDictionary<string, string> parameterValues, IMvxBundle presentationBundle = null, MvxRequestedBy requestedBy = null) where TViewModel : IMvxViewModel; protected bool ShowViewModel<TViewModel>(IMvxBundle parameterBundle = null, IMvxBundle presentationBundle = null, MvxRequestedBy requestedBy = null) where TViewModel : IMvxViewModel; protected bool ShowViewModel<TViewModel, TInit>(TInit parameter, IMvxBundle presentationBundle = null, MvxRequestedBy requestedBy = null) where TViewModel : IMvxViewModelInitializer<TInit>; }
在ViewModel的父类 MvxNavigationObject中,对ShowViewModel有多个重载,分别可以通过指定ViewModel的类型以及导航参数进行导航。MvvmCross通过指定的ViewModel的类型查找相关联的视图,构建或查找已经存在的视图,并构建或查找缓存中的ViewModel,将ViewModel作为视图的DataContext注入到视图中,并根据对视图显示的配置,显示相应的视图。通常情况下,视图都将做为一个窗口进行显示。
- 通过代码手动创建对象,直接调用ViewModel对象的构造函数进行创建。
有些情况下,我们可能会单独创建视图进行显示,这时视图不会自动创建相关联的ViewModel,这时我们就需要手工进行创建,并将创建的ViewModel赋值给视图的DataContext属性或是ViewModel属性。
var loginView = new LoginView(); var loginViewModl = new LoginViewModel(); loginView.ViewModel = loginViewModel;
初始化
在ViewModel的构造时我们提到在导航到指定的ViewModel时,可以传入导航参数,那么,导航参数如何在ViewModel中使用呢?这里就涉及到ViewModel的初始化方法 Init()。
初始化方法有以下约束:
- 名称必须是 Init并且无返回值;
- 参数可以有多个参数,参数类型是简单类型,如字符串、整型、浮点型、Guid、枚举
- 参数可以是一个对象,此对象的属性类型必须是简单类型
- 还有另外一种初始化的方法,重载 InitFromBundle 方法。
Init方法可以根据需要声明零到多个,MvvmCross将根据每次传入参数不同,匹配不同的初始化方法。
Init方法会在ViewModel对象被构造以后或者在 ReloadState 和 Start 方法之后被调用。
例如,我们的导航方法为:
ShowViewModel<DetailViewModel>(new { First="Hello", Second="World", Answer=42 });
那么我们可以这么获取导航参数:
public class DetailViewModel : MvxViewModel { // ... public void Init(string First, string Second, int Answer) { // use the values } // ... }
或者
public class DetailViewModel : MvxViewModel { // ... public class NavObject { public string First {get;set;} public string Second {get;set;} public int Answer {get;set;} } public void Init(NavObject navObject) { // use navObject } // ... }
或者
public class DetailViewModel : MvxViewModel { // ... public override void InitFromBundle(IMvxBundle bundle) { // use bundle - e.g. bundle.Data["First"] } // ... }
墓碑状态
墓碑是什么?说简单点,就是手机上一个任务被迫中断时(如有电话打入),系统记录下当前应用程序的状态后,(像把事件记录在墓碑上一样),然后中止程序。当需要恢复时,根据“墓碑”上的内容,将程序恢复到中断之前的状态。这样的一种机制就是“墓碑机制”。只是叫法不一样,实际在Android和iOS中都有类似的状态。在Android中叫Stop,在iOS中viewWillDisappear。
既然有墓碑状态,那么就可以从墓碑状态中恢复,ReloadFromBundle方法就是从墓碑状态中恢复保存的数据的方法。
启动
当ViewModel 从按顺序执行完构建、Init、ReloadState 恢复后,就会调用Start 方法。启动方法是一个无参的方法。
public class DetailViewModel : MvxViewModel { // ... public override void Start() { // do any start } // ... }
ViewModel导航
启动导航
启动导航,是指在系统启动时的第一个窗口。指定启动导航窗口,一般是在App.cs中指定:
Mvx.RegisterAppStart<TipViewModel>();
这样,当系统启动时,MvvmCross会查找指定ViewModel关联的View,并将查找到的View作用第一个窗口进行显示。
在Android系统中,可以指定 SplashScreenActivity 以实现启动页的功能。
- 删除目前启动首页的设置,在目前主窗口的Activity的Activity特性标签上,移除 MainLauncher=true,表示不再将主窗口作为App启动的第一个窗口。
- 添加新的启动页的布局:
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:text="loading..." /> </FrameLayout>
- 添加新的启动页的Activity。这里我们将
MainLauncher 设置为true,表示此窗口为App启动的第一个窗口。
代码如下:
using Android.App; using Cirrious.MvvmCross.Droid.Views; namespace CalcApp.UI.Droid { [Activity(Label = "My App", MainLauncher = true, NoHistory = true, Icon = "@drawable/icon")] public class SplashScreenActivity : MvxSplashScreenActivity { public SplashScreenActivity() : base(Resource.Layout.SplashScreen) { } } }
有时我们需要根据不同的场景,启动不同的启动页,这时通过指定单一的启动页就不能满足我们的需求。MvvmCross也支持自定义启动页。
- 实现自定义的启动对象:
public class CustomAppStart : MvxNavigatingObject, IMvxAppStart { private readonly ILoginService _service; public CustomAppStart(ILoginService service) { _service = service; } public void Start(object hint = null) { if (!_service.IsLoggedIn) { ShowViewModel<LoginViewModel>(); } else { ShowViewModel<TipViewModel>(); } } }
启动对象在启动时将根据用户的登录状态不同,显示不同的启动窗口。
- 使用自定义的启动对象进行启动,在App中,指定启动对象:
RegisterAppStart(CustomAppStart);
跳转导航
在ViewModel之间进行导航,可以通过ShowViewModel 方法进行导航。我们来看一下ShowViewModel的定义:
namespace MvvmCross.Core.ViewModels { public abstract class MvxNavigatingObject : MvxNotifyPropertyChanged { protected MvxNavigatingObject(); protected IMvxViewDispatcher ViewDispatcher { get; } protected bool ChangePresentation(MvxPresentationHint hint); protected bool Close(IMvxViewModel viewModel); protected bool ShowViewModel(Type viewModelType, object parameterValuesObject, IMvxBundle presentationBundle = null, MvxRequestedBy requestedBy = null); protected bool ShowViewModel(Type viewModelType, IDictionary<string, string> parameterValues, IMvxBundle presentationBundle = null, MvxRequestedBy requestedBy = null); protected bool ShowViewModel(Type viewModelType, IMvxBundle parameterBundle = null, IMvxBundle presentationBundle = null, MvxRequestedBy requestedBy = null); protected bool ShowViewModel<TViewModel>(object parameterValuesObject, IMvxBundle presentationBundle = null, MvxRequestedBy requestedBy = null) where TViewModel : IMvxViewModel; protected bool ShowViewModel<TViewModel>(IDictionary<string, string> parameterValues, IMvxBundle presentationBundle = null, MvxRequestedBy requestedBy = null) where TViewModel : IMvxViewModel; protected bool ShowViewModel<TViewModel>(IMvxBundle parameterBundle = null, IMvxBundle presentationBundle = null, MvxRequestedBy requestedBy = null) where TViewModel : IMvxViewModel; protected bool ShowViewModel<TViewModel, TInit>(TInit parameter, IMvxBundle presentationBundle = null, MvxRequestedBy requestedBy = null) where TViewModel : IMvxViewModelInitializer<TInit>; } }
- ShowViewModel有多个重载,可以根据需要,调用不同的重载。
- 在调用时可以传入导航参数,以达到在模块间传递参数的需要。
- 导航参数必须是简单类型的参数,包括整型、浮点型、字符串、布尔、枚举等类型。
- 在被导航的ViewModel中,需要定义相应的 Init 方法来接收导航参数。
- 在需要关闭指定的窗口时,可通过ViewModel中的 Close 方法进行关闭指定ViewModel相联的窗口。
小结
本篇主要讲解了ViewModel对象的生命周期以及如何通过ViewModel进行导航。如有问题请留言,我会尽快回复。
下节我们说下Android和iOS模拟器的配置问题。