相信做了开发有一定经验的人都知道IOC的存在,而Xamarin.Android中还没有IOC的存在。特别是在Xamarin.Android下,如果仅仅只是显示简单的数据,就需要通过很多的FindViewById来查找组件,并且还要负责呈现,今天我们将通过学习MVVMCross组件来简化这些操作,达到PCL部分的最大化,下面我们以一个官方的DEMO来学习。
由于公司的发展需要,需要招聘Xamarin方面的人才,如果你对Xamarin感兴趣的可以直接联系楼主,现在我们要召集8个人进行这方面的培训,所以只要有C#基础,并且有很强的兴趣都可以来。并且在产品完成后我们将会把开发的经验都撰写成博客发表,造福整个Xamarin(Q+976691141)。
首先我们需要新建一个名为“TipCalc.Core”的“类库(可移植)”的项目,并且我们需要按照下图选择对应的平台:
如果你的平台没有红色框住的部分也没有问题的,因为当前主流的PCL的方案是Profile259,如果你要查看你新建的类库是不是259可以查看项目的csproj文件,就是{项目名}.csproj,用记事本打开,然后定位到这个位置就可以看到了:
如果上面的PCL部分你按照我的选择之后,确定按钮是禁用的,就需要跟着下面的教程来解决这个问题,首先我们需要打开下面这个文件夹:
C:/Program Files (x86)/Reference Assemblies/Microsoft/Framework/.NETPortable/v4.5/Profile/Profile78/SupportedFrameworks
将该文件夹中的以下文件复制:
如果不存在“Xamarin.iOS.Unified.xml”也没有关系,之后我们打开下面这个文件夹:
C:/Program Files (x86)/Reference Assemblies/Microsoft/Framework/.NETPortable/v4.5/Profile/Profile259/SupportedFrameworks
将上面的文件复制到其中,然后重启VS就可以解决问题了。
删除Class1.cs 文件
我们并不需要使用该文件
我们打开“程序包管理器控制台”输入以下指令后回车:
Install-Package MvvmCross.HotTuna.MvvmCrossLibraries
添加Tip Calculation 服务
创建一个名为“Services”的文件夹,并在该文件夹中新建一个名为“ICalculation”的接口,具体的内容代码如下所示:
1 namespace TipCalc.Core.Services 2 { 3 public interface ICalculation 4 { 5 double TipAmount(double subTotal, int generosity); 6 } 7 }
并在该文件夹中新建一个Calculation类实现上面的接口,具体代码如下:
1 namespace TipCalc.Core.Services 2 { 3 public class Calculation : ICalculation 4 { 5 public double TipAmount(double subTotal, int generosity) 6 { 7 return subTotal * ((double)generosity)/100.0; 8 } 9 } 10 }
以上部分在实际项目中我们可以认为是应用服务层。
视图模型将会连接最终的界面与上面的服务层,而为了达到这些效果,我们的视图模型必须继承自MvxViewModel。下面我们先新建一个ViewModels文件夹并新建一个TipViewModel类,具体的代码如下所示:
1 namespace TipCalc.Core.ViewModels 2 { 3 public class TipViewModel : MvxViewModel 4 { 5 private readonly ICalculation _calculation; 6 public TipViewModel(ICalculation calculation) 7 { 8 _calculation = calculation; 9 } 10 11 public override void Start() 12 { 13 _subTotal = 100; 14 _generosity = 10; 15 Recalcuate(); 16 base.Start(); 17 } 18 19 private double _subTotal; 20 21 public double SubTotal 22 { 23 get { return _subTotal; } 24 set { _subTotal = value; RaisePropertyChanged(() => SubTotal); Recalcuate(); } 25 } 26 27 private int _generosity; 28 29 public int Generosity 30 { 31 get { return _generosity; } 32 set { _generosity = value; RaisePropertyChanged(() => Generosity); Recalcuate(); } 33 } 34 35 private double _tip; 36 37 public double Tip 38 { 39 get { return _tip; } 40 set { _tip = value; RaisePropertyChanged(() => Tip);} 41 } 42 43 private void Recalcuate() 44 { 45 Tip = _calculation.TipAmount(SubTotal, Generosity); 46 } 47 } 48 }
通过上面的代码我们可以看到,视图模型的构造函数要求传入一个实现了ICalculation接口的参数,而这个过程将会有IOC帮我们完成。剩下的我们可以看到我们重写了Start方法用来给视图模型中的值赋初始值,并且在每个值改变的同时还调用了Recalcuate方法来重新计算。
OK,完成这些之后,我们还需要一个启动项对依赖注入进行配置,并且指定初始显示的视图模型,我们直接在项目根目录下新建一个App类,并且该类需要继承自MvxApplication类,并在构造函数中写入以下代码:
1 namespace TipCalc.Core 2 { 3 public class App : MvxApplication 4 { 5 public App() 6 { 7 Mvx.RegisterType<ICalculation,Calculation>(); 8 Mvx.RegisterSingleton<IMvxAppStart>(new MvxAppStart<TipViewModel>()); 9 } 10 } 11 }
其中我们可以看到我们通过Mvx.RegisterType对IOC进行了配置,这样IOC才能够知道在需要ICalculation接口的时候实例化哪个类,而下一行的代码则是指定初始的视图模型,也就是首页,完成上面这些配置后我们的PCL部分完成了,可以通用于Android和IOS。
首先我们创建一个“Blank App(Android)”项目,项目名为“TipCalc.UI.Droid”。并将项目默认生成的MainActivity.cs文件和Resources/Layout文件夹下的Main.axml删除并添加TipCalc.Core.csproj引用。
跟PCL部分一样,我们还需要安装对应的类库:
Install-Package MvvmCross.HotTuna.MvvmCrossLibraries
添加MvvmCross Android 绑定资源
该部分是为了我们能够在界面中使用bind属性,所以我们需要在Resources/Values文件夹下新建一个xml资源,名字可以为“MvxBind”,对应的内容如下:
1 <?xml version="1.0" encoding="utf-8"?> 2 <resources> 3 <declare-styleable name="MvxBinding"> 4 <attr name="MvxBind" format="string"/> 5 <attr name="MvxLang" format="string"/> 6 </declare-styleable> 7 <declare-styleable name="MvxListView"> 8 <attr name="MvxItemTemplate" format="string"/> 9 <attr name="MvxDropDownItemTemplate" format="string"/> 10 </declare-styleable> 11 <item type="id" name="MvxBindingTagUnique"/> 12 <declare-styleable name="MvxImageView"> 13 <attr name="MvxSource" format="string"/> 14 </declare-styleable> 15 </resources>
虽然PCL中有启动项了,但是我们需要在特定中进行注册,所以我们还需要新建一个Setup类,并且该类的主要内容如下:
1 namespace TipCalc.UI.Droid 2 { 3 public class Setup : MvxAndroidSetup 4 { 5 public Setup(Context applicationContext) 6 : base(applicationContext) 7 { 8 } 9 10 protected override IMvxApplication CreateApp() 11 { 12 return new App(); 13 } 14 } 15 }
主要作用就是将App实例化并返回,以便在特定平台中进行初始化。
我们需要在Resources/Layout文件夹下新建一个视图名为View_Tip.axml,并且其中的内容如下所示:
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:local="http://schemas.android.com/apk/res/TipCalc.UI.Droid" 4 android:orientation="vertical" 5 android:layout_width="fill_parent" 6 android:layout_height="fill_parent"> 7 <TextView 8 android:layout_width="fill_parent" 9 android:layout_height="wrap_content" 10 android:text="SubTotal" /> 11 <EditText 12 android:layout_width="fill_parent" 13 android:layout_height="wrap_content" 14 local:MvxBind="Text SubTotal" /> 15 <TextView 16 android:layout_width="fill_parent" 17 android:layout_height="wrap_content" 18 android:text="Generosity" /> 19 <SeekBar 20 android:layout_width="fill_parent" 21 android:layout_height="wrap_content" 22 android:max="40" 23 local:MvxBind="Progress Generosity" /> 24 <View 25 android:layout_width="fill_parent" 26 android:layout_height="1dp" 27 android:background="#ffff00" /> 28 <TextView 29 android:layout_width="fill_parent" 30 android:layout_height="wrap_content" 31 android:text="Tip to leave" /> 32 <TextView 33 android:layout_width="fill_parent" 34 android:layout_height="wrap_content" 35 local:MvxBind="Text Tip" /> 36 </LinearLayout>
通过其中的内容我们可以看到,其中多了一个MvxBind属性,通过这个属性我们可以直接将控件的属性直接与ViewModel中对应的属性直接关联起来,这样我们可以省去与控件交互的部分,但是我们还是需要Activity来将界面与我们的ViewModel进行关联。
我们直接新建一个名为TipCalcView的活动,然后将其继承的类改成MvxActivity,具体的代码如下:
1 namespace TipCalc.UI.Droid.Views 2 { 3 [Activity(Label = "Tip", MainLauncher = true)] 4 public class TipView : MvxActivity 5 { 6 public new TipViewModel ViewModel 7 { 8 get { return (TipViewModel) base.ViewModel; } 9 set { base.ViewModel = value; } 10 } 11 12 protected override void OnViewModelSet() 13 { 14 base.OnViewModelSet(); 15 SetContentView(Resource.Layout.View_Tip); 16 } 17 } 18 }
完成这些之后,直接运行,我们可以看到调整进度条的同时,Tip to leave的值会跟着动,完全是实时的。
当然原生的方式与MvvmCross的方式是可以直接并存的。