@linux1s1s
2016-04-26T03:40:30.000000Z
字数 7574
阅读 2880
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
<layout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"><data><variable name="data" type="com.nilzor.presenterexample.MainModel"/></data><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:paddingLeft="@dimen/activity_horizontal_margin"android:paddingRight="@dimen/activity_horizontal_margin"android:paddingTop="@dimen/activity_vertical_margin"android:paddingBottom="@dimen/activity_vertical_margin"tools:context=".MainActivityFragment"><TextViewandroid:text="@{data.numberOfUsersLoggedIn}"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentEnd="true"android:id="@+id/loggedInUserCount"/><TextViewandroid:text="# logged in users:"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentEnd="false"android:layout_toLeftOf="@+id/loggedInUserCount"/><RadioGroupandroid:layout_marginTop="40dp"android:id="@+id/existingOrNewUser"android:gravity="center"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerHorizontal="true"android:orientation="horizontal"><RadioButtonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Returning user"android:checked="@{data.isExistingUserChecked}"android:id="@+id/returningUserRb"/><RadioButtonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="New user"android:id="@+id/newUserRb"/></RadioGroup><LinearLayoutandroid:orientation="horizontal"android:layout_width="match_parent"android:layout_height="wrap_content"android:id="@+id/username_block"android:layout_below="@+id/existingOrNewUser"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:textAppearance="?android:attr/textAppearanceMedium"android:text="Username:"android:id="@+id/textView"android:minWidth="100dp"/><EditTextandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:id="@+id/username"android:minWidth="200dp"/></LinearLayout><LinearLayoutandroid:orientation="horizontal"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_alignParentStart="false"android:id="@+id/password_block"android:layout_below="@+id/username_block"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:textAppearance="?android:attr/textAppearanceMedium"android:text="Password:"android:minWidth="100dp"/><EditTextandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:inputType="textPassword"android:ems="10"android:id="@+id/password"/></LinearLayout><LinearLayoutandroid:orientation="horizontal"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_below="@+id/password_block"android:id="@+id/email_block"android:visibility="@{data.emailBlockVisibility}"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:textAppearance="?android:attr/textAppearanceMedium"android:text="Email:"android:minWidth="100dp"/><EditTextandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:inputType="textEmailAddress"android:ems="10"android:id="@+id/email"/></LinearLayout><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{data.loginOrCreateButtonText}"android:id="@+id/loginOrCreateButton"android:layout_below="@+id/email_block"android:layout_centerHorizontal="true"/></RelativeLayout></layout>
MVVM – VIEW – JAVA
package com.nilzor.presenterexample;import android.app.Fragment;import android.os.Bundle;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.CompoundButton;import android.widget.Toast;import com.nilzor.presenterexample.databinding.FragmentMainBinding;public class MainActivityFragment extends Fragment {private FragmentMainBinding mBinding;private MainModel mViewModel;public MainActivityFragment() {}@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {View view = inflater.inflate(R.layout.fragment_main, container, false);mBinding = FragmentMainBinding.bind(view);mViewModel = new MainModel(this, getResources());mBinding.setData(mViewModel);attachButtonListener();return view;}private void attachButtonListener() {mBinding.loginOrCreateButton.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {mViewModel.logInClicked();}});}@Overridepublic void onViewCreated(View view, Bundle savedInstanceState) {ensureModelDataIsLodaded();}private void ensureModelDataIsLodaded() {if (!mViewModel.isLoaded()) {mViewModel.loadAsync();}}public void showShortToast(String text) {Toast.makeText(getActivity(), text, Toast.LENGTH_SHORT).show();}}
MVVM – VIEWMODEL
package com.nilzor.presenterexample;import android.content.res.Resources;import android.databinding.ObservableField;import android.os.AsyncTask;import android.view.View;import java.util.Random;public class MainModel {public ObservableField numberOfUsersLoggedIn = new ObservableField();public ObservableField isExistingUserChecked = new ObservableField();public ObservableField emailBlockVisibility = new ObservableField();public ObservableField loginOrCreateButtonText = new ObservableField();private boolean mIsLoaded;private MainActivityFragment mView;private Resources mResources;public MainModel(MainActivityFragment view, Resources resources) {mView = view;mResources = resources; // You might want to abstract this for testabilitysetInitialState();updateDependentViews();hookUpDependencies();}public boolean isLoaded() {return mIsLoaded;}private void setInitialState() {numberOfUsersLoggedIn.set("...");isExistingUserChecked.set(true);}private void hookUpDependencies() {isExistingUserChecked.addOnPropertyChangedCallback(new android.databinding.Observable.OnPropertyChangedCallback() {@Overridepublic void onPropertyChanged(android.databinding.Observable sender, int propertyId) {updateDependentViews();}});}public void updateDependentViews() {if (isExistingUserChecked.get()) {emailBlockVisibility.set(View.GONE);loginOrCreateButtonText.set(mResources.getString(R.string.log_in));}else {emailBlockVisibility.set(View.VISIBLE);loginOrCreateButtonText.set(mResources.getString(R.string.create_user));}}public void loadAsync() {new AsyncTask() {@Overrideprotected Void doInBackground(Void... params) {// Simulating some asynchronous task fetching data from a remote servertry {Thread.sleep(2000);} catch (Exception ex) {};numberOfUsersLoggedIn.set("" + new Random().nextInt(1000));mIsLoaded = true;return null;}}.execute((Void) null);}public void logInClicked() {// Illustrating the need for calling back to the view though testable interfaces.if (isExistingUserChecked.get()) {mView.showShortToast("Invalid username or password");}else {mView.showShortToast("Please enter a valid email address");}}}
附:
MVX Android设计架构浅析-MVC
MVX Android设计架构浅析-MVP
MVX Android设计架构浅析-MVVM