Xamarin Forms MvvM框架之FreshMvvM翻译一

原文地址:https://github.com/rid00z/FreshMvvm

FreshMvvm for Xamarin.Forms

FreshMvvm是专门为Xamarin.Forms设计的轻量Mvvm框架。 它是简单和灵活的。

与其他的选择相比怎么样?

  • 轻量,超简单
  • 它专为Xamarin.Forms设计
  • 设计易于学习和开发(当你还没有准备好RxUI时它是很好的)
  • 使用比配置更好的设定。

特点

  • PageModel到PageModel导航
  • BindingContext的自动构造
  • 页面事件的自动构造(例如页面出现)
  • PageModel上的基本方法(允许传值)(init,reverseinit)
  • 内置IOC容器(依赖注入)
  • PageModel构造函数注入
  • Basic中提供的基本方法,如弹消息框
  • 内置导航类型为SimpleNavigation,Tabbed和MasterDetail

它的故事

当Xamarin.Forms发布时,我(Michael Ridland)是Xamarin传统应用程序的一部分。 我想将项目移到Xamarin.Forms上,但是在该项目中我使用的是MvvMCross。 当时MvvmCross不支持Xamarin.Forms,所以我有几个选择

  • (1)适应MvvmCross
  • (2)找到一个替代的框架
  • (3)实现我自己的MvvM。

关于MvvmCross的最好的部分是它是双向数据绑定到原生的iOS / Android控件,但由于Xamarin.Forms已经拥有Databinding内置,这是没有用的,MvvMCross太大了,我不需要这么大。
我也无法找到一个可以轻松移动的替代方案。所以我做了属于我自己简单并且灵活MvvM框架。

它是从这个帖子开始的——翻译在这,为Xamarin.Forms实现自己的Mvvm。 我尽量为自己的MvvM框架做得简单。

从来没有想过来写一个框架,但在几次发布Mvvm解决方案之后,我发现很多人都想要它,并且似乎对此感兴趣。 另外考虑到我从Xamarin.Forms开始就在我所有的项目中使用了这个框架,我知道它的工作原理,所以我创建了FreshMvvm,于是它诞生了。

共同遵守的设定

  • 一个页面必须有一个相应的PageModel,类的命名十分重要,所以QuotePageModel必须有一个QuotePage页面。QuotePage上的BindingContext将被自动设置为Model
  • 一个PageModel可以拥有一个接收一个对象的Init方法
  • 一个PageModel可以有一个ReverseInit方法,它也可以使用一个对象,当一个模型被一个对象引用时被调用
  • PageModel可以将依赖关系自动注入到构造函数中

导航

FreshMvvm中的主要导航形式是PageModel到PageModel,这实际上意味着我们的观点不了解导航。

  • 所以要在PageModels之间导航使用:
await CoreMethods.PushPageModel<QuotePageModel>(); // 推送导航堆栈
await CoreMethods.PushPageModel<QuotePageModel>(null, true); // 推送模态
  • FreshMvvm中的导航引擎是通过一个简单的界面完成的,其中包含了Push和Pop的方法。 基本上这些方法可以以任何他们喜欢的方式控制应用程序的导航。
public interface IFreshNavigationService
{
   Task PushPage(Page page, FreshBasePageModel model, bool modal = false);
   Task PopPage(bool modal = false);
}
  • 在PushPage和PopPage中,您可以执行任何您喜欢的导航,这可以从简单的导航到高级嵌套导航。

该框架包含一些内置的导航容器,用于不同类型的导航。

  • 基本导航 - 内置

var page = FreshPageModelResolver.ResolvePageModel<MainMenuPageModel> ();
var basicNavContainer = new FreshNavigationContainer (page);
MainPage = basicNavContainer;
  • 主要细节 - 内置

var masterDetailNav = new FreshMasterDetailNavigationContainer ();
masterDetailNav.Init ("Menu");
masterDetailNav.AddPage<ContactListPageModel> ("Contacts", null);
masterDetailNav.AddPage<QuoteListPageModel> ("Pages", null);
MainPage = masterDetailNav;
  • 标签导航 - 内置

var tabbedNavigation = new FreshTabbedNavigationContainer ();
tabbedNavigation.AddTab<ContactListPageModel> ("Contacts", null);
tabbedNavigation.AddTab<QuoteListPageModel> ("Pages", null);
MainPage = tabbedNavigation;
  • 实施自定义导航

可以通过实现IFreshNavigationService.来设置任何类型的导航。在示例应用程序中有一个示例,名为CustomImplementedNav.cs。

示例应用程序

  • 基本导航例子
  • 标签式导航例子
  • MasterDetail导航例子
  • 使用MasterDetail Popover示例的Tabbed导航(这在Sample App中被称为CustomImplementedNav)

控制反转(IOC)

所以你不需要使用你自己的IOC容器,FreshMvvm自带了一个内置的IOC容器,它使用的是TinyIOC,但使用不同的命名来避免冲突。要在容器中注册服务注册:

FreshIOC.Container.Register<IDatabaseService, DatabaseService>();

注入时使用:

FreshIOC.Container.Resolve<IDatabaseService>();

这也是驱动构建器注入的方式。

我们现在流畅支持API来设置对象在IOC容器内的生命周期。

// 默认情况下,我们将具体类型注册为多例,并将接口注册为单例
FreshIOC.Container.Register<MyConcreteType>(); // 多例
FreshIOC.Container.Register<IMyInterface, MyConcreteType>(); // 单例

// Fluent API允许我们改变这种行为
FreshIOC.Container.Register<MyConcreteType>().AsSingleton(); // 单例
FreshIOC.Container.Register<IMyInterface, MyConcreteType>().AsMultiInstance(); // 多例

如下所示,IFreshIOC接口方法返回IRegisterOptions接口。

public interface IFreshIOC
{
    object Resolve(Type resolveType);
    IRegisterOptions Register<RegisterType>(RegisterType instance) where RegisterType : class;
    IRegisterOptions Register<RegisterType>(RegisterType instance, string name) where RegisterType : class;
    ResolveType Resolve<ResolveType>() where ResolveType : class;
    ResolveType Resolve<ResolveType>(string name) where ResolveType : class;
    IRegisterOptions Register<RegisterType, RegisterImplementation> ()  where RegisterType : class  where RegisterImplementation : class, RegisterType;
}

从register方法返回的接口是IRegisterOptions。

public interface IRegisterOptions
{
    IRegisterOptions AsSingleton();
    IRegisterOptions AsMultiInstance();
    IRegisterOptions WithWeakReference();
    IRegisterOptions WithStrongReference();
    IRegisterOptions UsingConstructor<RegisterType>(Expression<Func<RegisterType>> constructor);
}

PageModel - 构造函数注入

当PageModels被推送到IOC容器中的services可以被推入构造函数。

FreshIOC.Container.Register<IDatabaseService, DatabaseService>();

PageModel重要方法

/// <summary>
/// 以前的页面模型,这是自动推送填充
/// </summary>
public FreshBasePageModel PreviousPageModel { get; set; }
/// <summary>
/// 对当前页面的引用,即自动推送填充
/// </summary>
public Page CurrentPage { get; set; }
/// <summary>
/// 核心方法是应用程序的基本内置方法,包括推送,弹出和弹消息框
/// </summary>
public IPageModelCoreMethods CoreMethods { get; set; }    
/// <summary>
/// 当一个页面调用Pop'd这个方法时,它也允许返回数据。
/// </summary>
/// <param name="returndData">从...返回的数据 </param>
public virtual void ReverseInit(object returndData) { }
/// <summary>
/// 在加载PageModel时调用此方法,initData是之前从pagemodel发送来的数据
/// </summary>
/// <param name="initData">从推送器发送到此PageModel的数据</param>
public virtual void Init(object initData) { }
/// <summary>
/// View消失时调用此方法。
/// </summary>
protected virtual void ViewIsDisappearing (object sender, EventArgs e)
{
}
/// <summary>
/// View出现时调用此方法
/// </summary>
protected virtual void ViewIsAppearing (object sender, EventArgs e)
{
}

核心方法

每个PageModel都有一个名为“CoreMethods”的属性,当一个PageModel被推送时,它被自动填充,它是大多数应用程序需要的基本功能,如弹消息框,推送,弹出等。

public interface IPageModelCoreMethods
{
    Task DisplayAlert (string title, string message, string cancel);
    Task<string> DisplayActionSheet (string title, string cancel, string destruction, params string[] buttons);
    Task<bool> DisplayAlert (string title, string message, string accept, string cancel);
    Task PushPageModel<T>(object data, bool modal = false) where T : FreshBasePageModel;
    Task PopPageModel(bool modal = false);
    Task PopPageModel(object data, bool modal = false);
    Task PushPageModel<T>() where T : FreshBasePageModel;
}

Page的重要方法

PageModel Init PropertyChanged

示例PageModel

[ImplementPropertyChanged] // 使用Fody for Property更改通知
public class QuoteListPageModel : FreshBasePageModel
{
    IDatabaseService _databaseService;
    
    //这些通过构造函数注入IOC
    public QuoteListPageModel (IDatabaseService databaseService) 
    {
        _databaseService = databaseService;
    }
    
    public ObservableCollection<Quote> Quotes { get; set; }
    
    public override void Init (object initData)
    {
        Quotes = new ObservableCollection<Quote> (_databaseService.GetQuotes ());
    }
    
    //框架支持标准View出现和消失事件
    protected override void ViewIsAppearing (object sender, System.EventArgs e)
    {
        CoreMethods.DisplayAlert ("Page is appearing", "", "Ok");
        base.ViewIsAppearing (sender, e);
    }
    
    protected override void ViewIsDisappearing (object sender, System.EventArgs e)
    {
        base.ViewIsDisappearing (sender, e);
    }

    //跳转到另一个页面后,如果返回此页面调用
    public override void ReverseInit (object value)
    {
        var newContact = value as Quote;
        if (!Quotes.Contains (newContact))
        {
            Quotes.Add (newContact);
        }
    }

    public Command AddQuote 
    {
        get 
        {
            return new Command (async () => 
            {
                //Push A Page Model
                await CoreMethods.PushPageModel<QuotePageModel> ();
            });
        }
    }

    Quote _selectedQuote;

    public Quote SelectedQuote 
    {
        get 
        {
            return _selectedQuote;
        }
        set 
        {
            _selectedQuote = value;
            if (value != null)
                QuoteSelected.Execute (value);
        }
    }

    public Command<Quote> QuoteSelected 
    {
        get 
        {
            return new Command<Quote> (async (quote) => 
            {
                await CoreMethods.PushPageModel<QuotePageModel> (quote);
            });
        }
    }
}

多个导航服务

在FreshMvvm中可以进行任何类型的导航,通过实现自定义导航服务来完成自定义或高级场景。即使有这种能力,发现在FreshMvvm中做高级导航方案有点困难。在我回顾了FreshMvvm的所有支持问题之后,我发现人们的基本问题是他们希望能够多次使用我们内置的导航容器,其中两个主要例子是

  • (1)具有导航堆栈的主要细节:在一个master和另一个master的细节
  • (2)使用新的推动导航容器模型的能力。

为了支持这两种情况,我得出结论,FreshMvvm需要具有命名NavigationServices的能力,以便我们可以支持多个NavigationService。

使用多个导航容器

在下面我们运行一个单一的主细节的两个导航堆栈。

var masterDetailsMultiple = new MasterDetailPage (); //generic master detail page

//我们使用ContactList设置第一个导航容器
var contactListPage = FreshPageModelResolver.ResolvePageModel<ContactListPageModel> ();
contactListPage.Title = "Contact List";
//我们设置名为MasterPageArea的第一个导航容器
var masterPageArea = new FreshNavigationContainer (contactListPage, "MasterPageArea");
masterPageArea.Title = "Menu";

masterDetailsMultiple.Master = masterPageArea; //将第一个导航容器设置为Master

//我们使用QuoteList设置第二个导航容器
var quoteListPage = FreshPageModelResolver.ResolvePageModel<QuoteListPageModel> ();
quoteListPage.Title = "Quote List";
//我们设置名为DetailPageArea的第二个导航容器
var detailPageArea = new FreshNavigationContainer (quoteListPage, "DetailPageArea");

masterDetailsMultiple.Detail = detailPageArea; //将第二个导航容器设置为“Detail”

MainPage = masterDetailsMultiple;

在新的导航堆栈使用PushModally

//push a basic page Modally
var page = FreshPageModelResolver.ResolvePageModel<MainMenuPageModel> ();
var basicNavContainer = new FreshNavigationContainer (page, "secondNavPage");
await CoreMethods.PushNewNavigationServiceModal(basicNavContainer, new FreshBasePageModel[] { page.GetModel() }); 

//推送标签页模型
var tabbedNavigation = new FreshTabbedNavigationContainer ("secondNavPage");
tabbedNavigation.AddTab<ContactListPageModel> ("Contacts", "contacts.png", null);
tabbedNavigation.AddTab<QuoteListPageModel> ("Quotes", "document.png", null);
await CoreMethods.PushNewNavigationServiceModal(tabbedNavigation);

//推送主细节页面
var masterDetailNav = new FreshMasterDetailNavigationContainer ("secondNavPage");
masterDetailNav.Init ("Menu", "Menu.png");
masterDetailNav.AddPage<ContactListPageModel> ("Contacts", null);
masterDetailNav.AddPage<QuoteListPageModel> ("Quotes", null);
await CoreMethods.PushNewNavigationServiceModal(masterDetailNav);

在Xamarin.Forms MainPage上切换NavigationStacks

Xamarin.Forms中有些情况可能需要运行多个导航堆栈。 一个很好的例子是当你有一个用于认证的导航堆栈和一个应用程序主区域的堆栈。

首先我们可以为导航容器设置一些名称。

public class NavigationContainerNames
{
    public const string AuthenticationContainer = "AuthenticationContainer";
    public const string MainContainer = "MainContainer";
}

然后我们可以创建我们的两个导航容器并分配到主页面。

var loginPage = FreshMvvm.FreshPageModelResolver.ResolvePageModel<LoginViewModel>();
var loginContainer = new FreshNavigationContainer(loginPage, NavigationContainerNames.AuthenticationContainer);

var myPitchListViewContainer = new MainTabbedPage(NavigationContainerNames.MainContainer);

MainPage = loginContainer;

一旦我们设置好了,我们现在可以切换我们的导航容器。

CoreMethods.SwitchOutRootNavigation(NavigationContainerNames.MainContainer);

自定义IOC容器

FreshMvvm 1.0的第二个主要要求是允许自定义IOC容器。 在您的应用程序已经具有要使用的容器的情况下。

使用自定义IOC容器非常简单,因为您只需要实现单个接口。

public interface IFreshIOC
{
    object Resolve(Type resolveType);
    void Register<RegisterType>(RegisterType instance) where RegisterType : class;
    void Register<RegisterType>(RegisterType instance, string name) where RegisterType : class;
    ResolveType Resolve<ResolveType>() where ResolveType : class;
    ResolveType Resolve<ResolveType>(string name) where ResolveType : class;
    void Register<RegisterType, RegisterImplementation> () where RegisterType : class where RegisterImplementation : class, RegisterType;

然后在系统中设置IOC容器。

FreshIOC.OverrideContainer(myContainer);

相关视频/快速入门指南

  • FreshMvvm n = 0 - Mvvm在Xamarin.Forms和为什么需要FreshMvvm
  • FreshMvvm n = 1:你的第一个FreshMvvm应用程序
  • FreshMvvm n = 2 - IOC和构造器注入
  • FreshMvvm n = 3:在FreshMvvm中导航
  • 在FreshMvvm中为Xamarin.Forms实现自定义导航
  • TDD在Xamarin Studio - Live Coding FreshMvvm
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,966评论 25 707
  • Awesome DotNet,这又是一个 Awesome XXX 系列的资源整理,由 quozd 发起和维护。内容...
    小明yz阅读 3,691评论 0 47
  • 原文地址:http://www.michaelridland.com/xamarin/rolling-mvvm-x...
    本大少_阅读 426评论 0 0
  • 烦恼人生总叹春, 红花万点落红尘。 无聊作曲闲情寄, 多少哀伤那忍闻。 星影淡, 月清痕, 凄风冷雨打重门。 残灯...
    仙女下凡尘阅读 260评论 0 5
  • 这是从现在起,到2016年要完成的清单。你要像个小孩一样,有什么愿望就在睡觉前跟月亮说。然后第二天醒来自己去实现...
    Veromca5阅读 551评论 0 4