原文地址: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(); // 推送导航堆栈
await CoreMethods.PushPageModel(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 ();
var basicNavContainer = new FreshNavigationContainer (page);
MainPage = basicNavContainer;
-
主要细节 - 内置
var masterDetailNav = new FreshMasterDetailNavigationContainer ();
masterDetailNav.Init ("Menu");
masterDetailNav.AddPage ("Contacts", null);
masterDetailNav.AddPage ("Pages", null);
MainPage = masterDetailNav;
-
标签导航 - 内置
var tabbedNavigation = new FreshTabbedNavigationContainer ();
tabbedNavigation.AddTab ("Contacts", null);
tabbedNavigation.AddTab ("Pages", null);
MainPage = tabbedNavigation;
-
实施自定义导航
可以通过实现IFreshNavigationService.来设置任何类型的导航。在示例应用程序中有一个示例,名为CustomImplementedNav.cs。
示例应用程序
- 基本导航例子
- 标签式导航例子
- MasterDetail导航例子
- 使用MasterDetail Popover示例的Tabbed导航(这在Sample App中被称为CustomImplementedNav)
控制反转(IOC)
所以你不需要使用你自己的IOC容器,FreshMvvm自带了一个内置的IOC容器,它使用的是TinyIOC,但使用不同的命名来避免冲突。要在容器中注册服务注册:
FreshIOC.Container.Register();
注入时使用:
FreshIOC.Container.Resolve();
这也是驱动构建器注入的方式。
我们现在流畅支持API来设置对象在IOC容器内的生命周期。
// 默认情况下,我们将具体类型注册为多例,并将接口注册为单例
FreshIOC.Container.Register(); // 多例
FreshIOC.Container.Register(); // 单例
// Fluent API允许我们改变这种行为
FreshIOC.Container.Register().AsSingleton(); // 单例
FreshIOC.Container.Register().AsMultiInstance(); // 多例
如下所示,IFreshIOC接口方法返回IRegisterOptions接口。
public interface IFreshIOC
{
object Resolve(Type resolveType);
IRegisterOptions Register(RegisterType instance) where RegisterType : class;
IRegisterOptions Register(RegisterType instance, string name) where RegisterType : class;
ResolveType Resolve() where ResolveType : class;
ResolveType Resolve(string name) where ResolveType : class;
IRegisterOptions Register () where RegisterType : class where RegisterImplementation : class, RegisterType;
}
从register方法返回的接口是IRegisterOptions。
public interface IRegisterOptions
{
IRegisterOptions AsSingleton();
IRegisterOptions AsMultiInstance();
IRegisterOptions WithWeakReference();
IRegisterOptions WithStrongReference();
IRegisterOptions UsingConstructor(Expression> constructor);
}
PageModel - 构造函数注入
当PageModels被推送到IOC容器中的services可以被推入构造函数。
FreshIOC.Container.Register();
PageModel重要方法
///
/// 以前的页面模型,这是自动推送填充
///
public FreshBasePageModel PreviousPageModel { get; set; }
///
/// 对当前页面的引用,即自动推送填充
///
public Page CurrentPage { get; set; }
///
/// 核心方法是应用程序的基本内置方法,包括推送,弹出和弹消息框
///
public IPageModelCoreMethods CoreMethods { get; set; }
///
/// 当一个页面调用Pop'd这个方法时,它也允许返回数据。
///
/// 从...返回的数据
public virtual void ReverseInit(object returndData) { }
///
/// 在加载PageModel时调用此方法,initData是之前从pagemodel发送来的数据
///
/// 从推送器发送到此PageModel的数据
public virtual void Init(object initData) { }
///
/// View消失时调用此方法。
///
protected virtual void ViewIsDisappearing (object sender, EventArgs e)
{
}
///
/// View出现时调用此方法
///
protected virtual void ViewIsAppearing (object sender, EventArgs e)
{
}
核心方法
每个PageModel都有一个名为“CoreMethods”的属性,当一个PageModel被推送时,它被自动填充,它是大多数应用程序需要的基本功能,如弹消息框,推送,弹出等。
public interface IPageModelCoreMethods
{
Task DisplayAlert (string title, string message, string cancel);
Task DisplayActionSheet (string title, string cancel, string destruction, params string[] buttons);
Task DisplayAlert (string title, string message, string accept, string cancel);
Task PushPageModel(object data, bool modal = false) where T : FreshBasePageModel;
Task PopPageModel(bool modal = false);
Task PopPageModel(object data, bool modal = false);
Task PushPageModel() 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 Quotes { get; set; }
public override void Init (object initData)
{
Quotes = new ObservableCollection (_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 ();
});
}
}
Quote _selectedQuote;
public Quote SelectedQuote
{
get
{
return _selectedQuote;
}
set
{
_selectedQuote = value;
if (value != null)
QuoteSelected.Execute (value);
}
}
public Command QuoteSelected
{
get
{
return new Command (async (quote) =>
{
await CoreMethods.PushPageModel (quote);
});
}
}
}
多个导航服务
在FreshMvvm中可以进行任何类型的导航,通过实现自定义导航服务来完成自定义或高级场景。即使有这种能力,发现在FreshMvvm中做高级导航方案有点困难。在我回顾了FreshMvvm的所有支持问题之后,我发现人们的基本问题是他们希望能够多次使用我们内置的导航容器,其中两个主要例子是
- (1)具有导航堆栈的主要细节:在一个master和另一个master的细节
- (2)使用新的推动导航容器模型的能力。
为了支持这两种情况,我得出结论,FreshMvvm需要具有命名NavigationServices的能力,以便我们可以支持多个NavigationService。
使用多个导航容器
在下面我们运行一个单一的主细节的两个导航堆栈。
var masterDetailsMultiple = new MasterDetailPage (); //generic master detail page
//我们使用ContactList设置第一个导航容器
var contactListPage = FreshPageModelResolver.ResolvePageModel ();
contactListPage.Title = "Contact List";
//我们设置名为MasterPageArea的第一个导航容器
var masterPageArea = new FreshNavigationContainer (contactListPage, "MasterPageArea");
masterPageArea.Title = "Menu";
masterDetailsMultiple.Master = masterPageArea; //将第一个导航容器设置为Master
//我们使用QuoteList设置第二个导航容器
var quoteListPage = FreshPageModelResolver.ResolvePageModel ();
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 ();
var basicNavContainer = new FreshNavigationContainer (page, "secondNavPage");
await CoreMethods.PushNewNavigationServiceModal(basicNavContainer, new FreshBasePageModel[] { page.GetModel() });
//推送标签页模型
var tabbedNavigation = new FreshTabbedNavigationContainer ("secondNavPage");
tabbedNavigation.AddTab ("Contacts", "contacts.png", null);
tabbedNavigation.AddTab ("Quotes", "document.png", null);
await CoreMethods.PushNewNavigationServiceModal(tabbedNavigation);
//推送主细节页面
var masterDetailNav = new FreshMasterDetailNavigationContainer ("secondNavPage");
masterDetailNav.Init ("Menu", "Menu.png");
masterDetailNav.AddPage ("Contacts", null);
masterDetailNav.AddPage ("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();
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 instance) where RegisterType : class;
void Register(RegisterType instance, string name) where RegisterType : class;
ResolveType Resolve() where ResolveType : class;
ResolveType Resolve(string name) where ResolveType : class;
void Register () 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