【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"]
  }

  // ...
}

墓碑状态

Crying face墓碑是什么?说简单点,就是手机上一个任务被迫中断时(如有电话打入),系统记录下当前应用程序的状态后,(像把事件记录在墓碑上一样),然后中止程序。当需要恢复时,根据“墓碑”上的内容,将程序恢复到中断之前的状态。这样的一种机制就是“墓碑机制”。只是叫法不一样,实际在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模拟器的配置问题。

原文地址:https://www.cnblogs.com/phoenixdong/p/6557500.html