0x01 前言
就目前而言,MVVM可以说是挺流行的,无论是web端还是移动端,web端的主要代表angularjs,avalonjs等,
移动端(xamarin,uwp)的代表应该是mvvmlight,mvvmcross等,
我们的主题是移动端,所以主要讲mvvmlight,mvvmcross,这篇主要讲MvvmLight,下篇讲MvvmCross。
还是以Demo的形式来谈使用。
0x02 简单的MVVM(mvvmlight) Demo
先来个web版最简单的MVVM效果,然后在按xamarin.android->uwp的顺序做一样效果的demo
注:这个效果是基于 avalonjs的
下面来看看我们的第一个例子(Xamarin.Android):
新建一个Android项目Catcher.MVVMDemo.Day01DroidByMvvmLight
通过NuGet安装相关组件(MvvmLight)。
然后编写我们的Main.axml
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:orientation="vertical" 4 android:layout_width="fill_parent" 5 android:layout_height="fill_parent"> 6 <EditText 7 android:layout_width="fill_parent" 8 android:layout_height="wrap_content" 9 android:id="@+id/et_input" /> 10 <TextView 11 android:layout_width="fill_parent" 12 android:layout_height="wrap_content" 13 android:id="@+id/tv_input" /> 14 </LinearLayout>
然后去修改MainActivity
1 using Android.App; 2 using Android.OS; 3 using Android.Widget; 4 using GalaSoft.MvvmLight.Helpers; 5 using GalaSoft.MvvmLight.Views; 6 namespace Catcher.MVVMDemo.Day01DroidByMvvmLight 7 { 8 [Activity(Label = "MvvmLightDemo", MainLauncher = true, Icon = "@drawable/icon")] 9 public class MainActivity : ActivityBase 10 { 11 EditText etInput; 12 TextView tvInput; 13 protected override void OnCreate(Bundle bundle) 14 { 15 base.OnCreate(bundle); 16 SetContentView(Resource.Layout.Main); 17 etInput = FindViewById<EditText>(Resource.Id.et_input); 18 tvInput = FindViewById<TextView>(Resource.Id.tv_input); 19 20 this.SetBinding(() => etInput.Text, () => tvInput.Text); 21 } 22 } 23 }
MainActivity是继承ActivityBase,同时将输入的值绑定在TextView上。
效果图如下:
第二个例子(UWP):
新建一个Universal Windows项目:Catcher.MVVMDemo.Day01UWP
修改我们的MainPage.xaml
1 <Page 2 x:Class="Catcher.MVVMDemo.Day01UWP.MainPage" 3 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 4 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 5 xmlns:local="using:Catcher.MVVMDemo.Day01UWP" 6 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 7 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 8 mc:Ignorable="d"> 9 10 <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> 11 <StackPanel VerticalAlignment="Top"> 12 <TextBox x:Name="txtName"/> 13 <TextBlock Text="{Binding ElementName=txtName,Path=Text}"/> 14 </StackPanel> 15 </Grid> 16 </Page>
这里直接在页面通过Binding来绑定了。相比Android简洁了不少。
效果如下:
到这里,这两个简单的例子已经OK了,你是不是也想动手试试呢!
不过这两个例子并没有涉及到Mvvm主要的东西。至少连ViewModel的影子都还没出现呢。
0x03 MVVM(mvvmlight) 登陆Demo
开始之前,我们新建一个类库项目Catcher.MVVMDemo.Day01Core
这个类库是后面的2个例子都要用到的,处理我们的ViewModel。
通过NuGet安装MvvmLight
在ViewModel文件夹下面添加一个LoginViewModel
1 using GalaSoft.MvvmLight; 2 using GalaSoft.MvvmLight.Command; 3 using GalaSoft.MvvmLight.Messaging; 4 using GalaSoft.MvvmLight.Views; 5 using Microsoft.Practices.ServiceLocation; 6 using System.Diagnostics; 7 namespace Catcher.MVVMDemo.Day01Core.ViewModel 8 { 9 public class LoginViewModel : ViewModelBase 10 { 11 public LoginViewModel() 12 { 13 } 14 private string _name; 15 public string Name 16 { 17 get 18 { 19 return _name; 20 } 21 set 22 { 23 _name = value; 24 //RaisePropertyChanged("Name"); 25 RaisePropertyChanged(() => Name); 26 } 27 } 28 private string _password; 29 public string Password 30 { 31 get 32 { 33 return _password; 34 } 35 set 36 { 37 _password = value; 38 RaisePropertyChanged(() => Password); 39 } 40 } 41 /// <summary> 42 /// login command 43 /// </summary> 44 public RelayCommand LoginCommand 45 { 46 get 47 { 48 return new RelayCommand(() => Login()); 49 } 50 } 51 /// <summary> 52 /// login 53 /// </summary> 54 private void Login() 55 { 56 //Valid the user 57 if (Name == "catcher" && Password == "123") 58 { 59 var nav = ServiceLocator.Current.GetInstance<INavigationService>(); 60 nav.NavigateTo("Main"); 61 } 62 else 63 { 64 var dialog = ServiceLocator.Current.GetInstance<IDialogService>(); 65 dialog.ShowMessage( 66 "check your name and password", 67 "infomation", 68 "OK", 69 null); 70 } 71 } 72 } 73 }
这里的登陆是写死了一个用户名和密码。
同时,修改我们的ViewModelLocator,添加我们LoginViewModel的信息
1 using GalaSoft.MvvmLight.Ioc; 2 using Microsoft.Practices.ServiceLocation; 3 namespace Catcher.MVVMDemo.Day01Core.ViewModel 4 { 5 public class ViewModelLocator 6 { 7 public ViewModelLocator() 8 { 9 //provider 10 ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default); 11 //view model 12 SimpleIoc.Default.Register<MainViewModel>(); 13 SimpleIoc.Default.Register<LoginViewModel>(); 14 } 15 public MainViewModel Main 16 { 17 get 18 { 19 return ServiceLocator.Current.GetInstance<MainViewModel>(); 20 } 21 } 22 public LoginViewModel LoginViewModel 23 { 24 get 25 { 26 return ServiceLocator.Current.GetInstance<LoginViewModel>(); 27 } 28 } 29 public static void Cleanup() 30 { 31 } 32 } 33 }
到这里,我们将ViewModel的相关处理做好了。
下面两个例子就是添加一个登陆页面,提供验证,登陆成功就跳转到我们前面两个例子的页面,不成功就弹框提示。
第三个例子(Xamarin.Android):
在刚才的Catcher.MVVMDemo.Day01DroidByMvvmLight中,添加一个App.cs,主要是注册一些东西
1 using Catcher.MVVMDemo.Day01Core.ViewModel; 2 using GalaSoft.MvvmLight.Views; 3 using GalaSoft.MvvmLight.Ioc; 4 namespace Catcher.MVVMDemo.Day01DroidByMvvmLight 5 { 6 public static class App 7 { 8 private static ViewModelLocator _locator; 9 public static ViewModelLocator Locator 10 { 11 get 12 { 13 if (_locator == null) 14 { 15 var nav = new NavigationService(); 16 nav.Configure("Main", typeof(MainActivity)); 17 18 SimpleIoc.Default.Register<INavigationService>(() => nav); 19 //the dialog 20 SimpleIoc.Default.Register<IDialogService, DialogService>(); 21 _locator = new ViewModelLocator(); 22 } 23 return _locator; 24 } 25 } 26 } 27 }
添加一个login.axml
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:orientation="vertical" 4 android:layout_width="match_parent" 5 android:layout_height="match_parent"> 6 <EditText 7 android:layout_width="match_parent" 8 android:layout_height="wrap_content" 9 android:hint="enter your name" 10 android:id="@+id/et_name" /> 11 <EditText 12 android:layout_width="match_parent" 13 android:layout_height="wrap_content" 14 android:inputType="textPassword" 15 android:hint="enter your password" 16 android:id="@+id/et_pwd" /> 17 <Button 18 android:layout_width="match_parent" 19 android:layout_height="wrap_content" 20 android:text="Login" 21 android:id="@+id/btn_login" /> 22 </LinearLayout>
添加一个LoginActivity,与LoginViewModel相适配。
1 using Android.App; 2 using Android.OS; 3 using Android.Widget; 4 using Catcher.MVVMDemo.Day01Core.ViewModel; 5 using GalaSoft.MvvmLight.Helpers; 6 using GalaSoft.MvvmLight.Views; 7 namespace Catcher.MVVMDemo.Day01DroidByMvvmLight 8 { 9 [Activity(Label = "Login", MainLauncher = true, Icon = "@drawable/icon")] 10 public class LoginActivity : ActivityBase 11 { 12 /// <summary> 13 /// the view model 14 /// </summary> 15 public LoginViewModel VM 16 { 17 get { return App.Locator.LoginViewModel; } 18 } 19 protected override void OnCreate(Bundle savedInstanceState) 20 { 21 base.OnCreate(savedInstanceState); 22 SetContentView(Resource.Layout.login); 23 EditText etName = FindViewById<EditText>(Resource.Id.et_name); 24 EditText etPassword = FindViewById<EditText>(Resource.Id.et_pwd); 25 Button btnLogin = FindViewById<Button>(Resource.Id.btn_login); 26 //binding 27 this.SetBinding(() => VM.Name, etName, () => etName.Text, BindingMode.TwoWay); 28 this.SetBinding(() => VM.Password, etPassword, () => etPassword.Text, BindingMode.TwoWay); 29 //button click 30 btnLogin.SetCommand("Click", VM.LoginCommand); 31 } 32 } 33 }
VM通过App.cs里面的来获取。
两个输入框的绑定方式设为TwoWay。
按钮的点击事件设为LoginViewModel的LoginCommand。
最后去掉MainActivity的MainLauncher=true
效果图如下:
第四个例子(UWP):
在刚才的Catcher.MVVMDemo.Day01UWP中,添加一个LoginPage.xaml
1 <Page 2 x:Class="Catcher.MVVMDemo.Day01UWP.LoginPage" 3 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 4 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 5 xmlns:local="using:Catcher.MVVMDemo.Day01UWP" 6 xmlns:vm="using:Catcher.MVVMDemo.Day01Core.ViewModel" 7 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 8 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 9 mc:Ignorable="d"> 10 <Page.DataContext> 11 <vm:LoginViewModel /> 12 </Page.DataContext> 13 <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> 14 <Grid.RowDefinitions> 15 <RowDefinition Height="*"></RowDefinition> 16 <RowDefinition Height="*"></RowDefinition> 17 <RowDefinition Height="*"></RowDefinition> 18 <RowDefinition Height="*"></RowDefinition> 19 <RowDefinition Height="5*"></RowDefinition> 20 </Grid.RowDefinitions> 21 <TextBox Grid.Row="1" Margin="15" Height="20" Text="{Binding Name,Mode=TwoWay}" PlaceholderText="enter you name" /> 22 <PasswordBox Grid.Row="2" Margin="15" Height="20" Password="{Binding Password,Mode=TwoWay}" PasswordChar="*" PlaceholderText="enter your password" /> 23 <Button Grid.Row="3" Margin="15,10" Content="Login" Command="{Binding LoginCommand}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" /> 24 </Grid> 25 </Page>
通过Page.DataContext设置了ViewModel
对TextBox,PasswordBox和button进行了相应的绑定。
然后修改App.xaml.cs中的OnLaunched方法,主要是启动页面和注册MvvmLight的东西
1 protected override void OnLaunched(LaunchActivatedEventArgs e) 2 { 3 #if DEBUG 4 if (System.Diagnostics.Debugger.IsAttached) 5 { 6 this.DebugSettings.EnableFrameRateCounter = true; 7 } 8 #endif 9 Frame rootFrame = Window.Current.Content as Frame; 10 if (rootFrame == null) 11 { 12 rootFrame = new Frame(); 13 rootFrame.NavigationFailed += OnNavigationFailed; 14 if (e.PreviousExecutionState == ApplicationExecutionState.Terminated) 15 { 16 } 17 18 Window.Current.Content = rootFrame; 19 } 20 if (e.PrelaunchActivated == false) 21 { 22 if (rootFrame.Content == null) 23 { 24 rootFrame.Navigate(typeof(LoginPage), e.Arguments); 25 } 26 27 Window.Current.Activate(); 28 // 29 ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default); 30 var navigationService = new NavigationService(); 31 navigationService.Configure("Login", typeof(LoginPage)); 32 navigationService.Configure("Main", typeof(MainPage)); 33 SimpleIoc.Default.Register<INavigationService>(() => navigationService); 34 SimpleIoc.Default.Register<IDialogService, DialogService>(); 35 } 36 }
效果图:
0x04 简单总结
对于Android来说,主要以下几个点:
1.Activity是继承了MvvmLight自己实现的ActivityBase,具体如下:
1 namespace GalaSoft.MvvmLight.Views 2 { 3 public class ActivityBase : Activity 4 { 5 public ActivityBase(); 6 public static ActivityBase CurrentActivity { get; } 7 public static void GoBack(); 8 protected override void OnResume(); 9 } 10 }
2.ViewModel继承ViewModelBase这个抽象类,在深究必然离不开INotifyPropertyChanged这个接口。
1 public abstract class ViewModelBase : ObservableObject, ICleanup 2 3 public class ObservableObject : INotifyPropertyChanged
3.在ViewModelLocator里面通过SimpleIoc注册我们的ViewModel,当然也可以用Autofac等。
4.SetBinding和SetCommand的应用,可以看看具体的实现
对UWP来说,除了公共部分,与Android的区别就是在xaml中绑定了属性和“事件”。
下一篇会讲讲MvvmCross的简单使用。