[关闭]
@linux1s1s 2016-04-26T03:40:30.000000Z 字数 7574 阅读 1912

MVX Android设计架构浅析-MVVM

Android_Architecture 2016-04


简介

读过MVX Android设计架构浅析的童鞋应该还记得2005年微软工程师John Gossman在自己的博客上首次公布了MVVM模式。时隔10年之久才在Android活跃起来,究其原因是之前Android并不支持Data-binding,所以在了解MVVM之前很有必要对Data-binding有个充分的认识。当然这里不是重点,所以不再深究。

那么MVVM和前篇博客中介绍的MVX Android设计架构浅析-MVP有啥区别呢?下面用两张简单的交互图解释一下。

此处输入图片的描述

此处输入图片的描述

如果上面两张图看的不太明白,可以复习一下MVX Android设计架构浅析-MVP其中的代码部分介绍的很清楚。

简单的说一下上面MVVM的交互图
可以看到对view中数据的所有绑定和更新操作都是通过Data Binding框架实现的。通过ObservableField类,View在model发生变化时会作出反应,在XML文件中对属性的引用使得框架在用户操作View时可以将变化推送给对应的ViewModel。我们也可以通过代码订阅属性的变化,这样可以实现例如当CheckBox被点击后,TextView被禁用这样的功能。像这样使用标准Java类来表示View的视觉状态的一个很大优势是明显的:你可以很容易对这种视觉行为进行单元测试。

简单交互图

我们将上面的图示经过精简一下MVVM的交互就简单表示如下
此处输入图片的描述

代码

Talk is cheap,show me the code.(废话少说,直接上代码)

MVVM – VIEW – XML

  1. <layout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools">
  3. <data>
  4. <variable name="data" type="com.nilzor.presenterexample.MainModel"/>
  5. </data>
  6. <RelativeLayout
  7. android:layout_width="match_parent"
  8. android:layout_height="match_parent"
  9. android:paddingLeft="@dimen/activity_horizontal_margin"
  10. android:paddingRight="@dimen/activity_horizontal_margin"
  11. android:paddingTop="@dimen/activity_vertical_margin"
  12. android:paddingBottom="@dimen/activity_vertical_margin"
  13. tools:context=".MainActivityFragment">
  14. <TextView
  15. android:text="@{data.numberOfUsersLoggedIn}"
  16. android:layout_width="wrap_content"
  17. android:layout_height="wrap_content"
  18. android:layout_alignParentEnd="true"
  19. android:id="@+id/loggedInUserCount"/>
  20. <TextView
  21. android:text="# logged in users:"
  22. android:layout_width="wrap_content"
  23. android:layout_height="wrap_content"
  24. android:layout_alignParentEnd="false"
  25. android:layout_toLeftOf="@+id/loggedInUserCount"/>
  26. <RadioGroup
  27. android:layout_marginTop="40dp"
  28. android:id="@+id/existingOrNewUser"
  29. android:gravity="center"
  30. android:layout_width="wrap_content"
  31. android:layout_height="wrap_content"
  32. android:layout_centerHorizontal="true"
  33. android:orientation="horizontal">
  34. <RadioButton
  35. android:layout_width="wrap_content"
  36. android:layout_height="wrap_content"
  37. android:text="Returning user"
  38. android:checked="@{data.isExistingUserChecked}"
  39. android:id="@+id/returningUserRb"/>
  40. <RadioButton
  41. android:layout_width="wrap_content"
  42. android:layout_height="wrap_content"
  43. android:text="New user"
  44. android:id="@+id/newUserRb"
  45. />
  46. </RadioGroup>
  47. <LinearLayout
  48. android:orientation="horizontal"
  49. android:layout_width="match_parent"
  50. android:layout_height="wrap_content"
  51. android:id="@+id/username_block"
  52. android:layout_below="@+id/existingOrNewUser">
  53. <TextView
  54. android:layout_width="wrap_content"
  55. android:layout_height="wrap_content"
  56. android:textAppearance="?android:attr/textAppearanceMedium"
  57. android:text="Username:"
  58. android:id="@+id/textView"
  59. android:minWidth="100dp"/>
  60. <EditText
  61. android:layout_width="wrap_content"
  62. android:layout_height="wrap_content"
  63. android:id="@+id/username"
  64. android:minWidth="200dp"/>
  65. </LinearLayout>
  66. <LinearLayout
  67. android:orientation="horizontal"
  68. android:layout_width="match_parent"
  69. android:layout_height="wrap_content"
  70. android:layout_alignParentStart="false"
  71. android:id="@+id/password_block"
  72. android:layout_below="@+id/username_block">
  73. <TextView
  74. android:layout_width="wrap_content"
  75. android:layout_height="wrap_content"
  76. android:textAppearance="?android:attr/textAppearanceMedium"
  77. android:text="Password:"
  78. android:minWidth="100dp"/>
  79. <EditText
  80. android:layout_width="wrap_content"
  81. android:layout_height="wrap_content"
  82. android:inputType="textPassword"
  83. android:ems="10"
  84. android:id="@+id/password"/>
  85. </LinearLayout>
  86. <LinearLayout
  87. android:orientation="horizontal"
  88. android:layout_width="match_parent"
  89. android:layout_height="wrap_content"
  90. android:layout_below="@+id/password_block"
  91. android:id="@+id/email_block"
  92. android:visibility="@{data.emailBlockVisibility}">
  93. <TextView
  94. android:layout_width="wrap_content"
  95. android:layout_height="wrap_content"
  96. android:textAppearance="?android:attr/textAppearanceMedium"
  97. android:text="Email:"
  98. android:minWidth="100dp"/>
  99. <EditText
  100. android:layout_width="wrap_content"
  101. android:layout_height="wrap_content"
  102. android:inputType="textEmailAddress"
  103. android:ems="10"
  104. android:id="@+id/email"/>
  105. </LinearLayout>
  106. <Button
  107. android:layout_width="wrap_content"
  108. android:layout_height="wrap_content"
  109. android:text="@{data.loginOrCreateButtonText}"
  110. android:id="@+id/loginOrCreateButton"
  111. android:layout_below="@+id/email_block"
  112. android:layout_centerHorizontal="true"/>
  113. </RelativeLayout>
  114. </layout>

MVVM – VIEW – JAVA

  1. package com.nilzor.presenterexample;
  2. import android.app.Fragment;
  3. import android.os.Bundle;
  4. import android.view.LayoutInflater;
  5. import android.view.View;
  6. import android.view.ViewGroup;
  7. import android.widget.CompoundButton;
  8. import android.widget.Toast;
  9. import com.nilzor.presenterexample.databinding.FragmentMainBinding;
  10. public class MainActivityFragment extends Fragment {
  11. private FragmentMainBinding mBinding;
  12. private MainModel mViewModel;
  13. public MainActivityFragment() {
  14. }
  15. @Override
  16. public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
  17. View view = inflater.inflate(R.layout.fragment_main, container, false);
  18. mBinding = FragmentMainBinding.bind(view);
  19. mViewModel = new MainModel(this, getResources());
  20. mBinding.setData(mViewModel);
  21. attachButtonListener();
  22. return view;
  23. }
  24. private void attachButtonListener() {
  25. mBinding.loginOrCreateButton.setOnClickListener(new View.OnClickListener() {
  26. @Override
  27. public void onClick(View v) {
  28. mViewModel.logInClicked();
  29. }
  30. });
  31. }
  32. @Override
  33. public void onViewCreated(View view, Bundle savedInstanceState) {
  34. ensureModelDataIsLodaded();
  35. }
  36. private void ensureModelDataIsLodaded() {
  37. if (!mViewModel.isLoaded()) {
  38. mViewModel.loadAsync();
  39. }
  40. }
  41. public void showShortToast(String text) {
  42. Toast.makeText(getActivity(), text, Toast.LENGTH_SHORT).show();
  43. }
  44. }

MVVM – VIEWMODEL

  1. package com.nilzor.presenterexample;
  2. import android.content.res.Resources;
  3. import android.databinding.ObservableField;
  4. import android.os.AsyncTask;
  5. import android.view.View;
  6. import java.util.Random;
  7. public class MainModel {
  8. public ObservableField numberOfUsersLoggedIn = new ObservableField();
  9. public ObservableField isExistingUserChecked = new ObservableField();
  10. public ObservableField emailBlockVisibility = new ObservableField();
  11. public ObservableField loginOrCreateButtonText = new ObservableField();
  12. private boolean mIsLoaded;
  13. private MainActivityFragment mView;
  14. private Resources mResources;
  15. public MainModel(MainActivityFragment view, Resources resources) {
  16. mView = view;
  17. mResources = resources; // You might want to abstract this for testability
  18. setInitialState();
  19. updateDependentViews();
  20. hookUpDependencies();
  21. }
  22. public boolean isLoaded() {
  23. return mIsLoaded;
  24. }
  25. private void setInitialState() {
  26. numberOfUsersLoggedIn.set("...");
  27. isExistingUserChecked.set(true);
  28. }
  29. private void hookUpDependencies() {
  30. isExistingUserChecked.addOnPropertyChangedCallback(new android.databinding.Observable.OnPropertyChangedCallback() {
  31. @Override
  32. public void onPropertyChanged(android.databinding.Observable sender, int propertyId) {
  33. updateDependentViews();
  34. }
  35. });
  36. }
  37. public void updateDependentViews() {
  38. if (isExistingUserChecked.get()) {
  39. emailBlockVisibility.set(View.GONE);
  40. loginOrCreateButtonText.set(mResources.getString(R.string.log_in));
  41. }
  42. else {
  43. emailBlockVisibility.set(View.VISIBLE);
  44. loginOrCreateButtonText.set(mResources.getString(R.string.create_user));
  45. }
  46. }
  47. public void loadAsync() {
  48. new AsyncTask() {
  49. @Override
  50. protected Void doInBackground(Void... params) {
  51. // Simulating some asynchronous task fetching data from a remote server
  52. try {Thread.sleep(2000);} catch (Exception ex) {};
  53. numberOfUsersLoggedIn.set("" + new Random().nextInt(1000));
  54. mIsLoaded = true;
  55. return null;
  56. }
  57. }.execute((Void) null);
  58. }
  59. public void logInClicked() {
  60. // Illustrating the need for calling back to the view though testable interfaces.
  61. if (isExistingUserChecked.get()) {
  62. mView.showShortToast("Invalid username or password");
  63. }
  64. else {
  65. mView.showShortToast("Please enter a valid email address");
  66. }
  67. }
  68. }

附:
MVX Android设计架构浅析-MVC
MVX Android设计架构浅析-MVP
MVX Android设计架构浅析-MVVM

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注