@linux1s1s
2016-04-26T11:40:30.000000Z
字数 7574
阅读 2510
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>
<RelativeLayout
android: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">
<TextView
android:text="@{data.numberOfUsersLoggedIn}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:id="@+id/loggedInUserCount"/>
<TextView
android:text="# logged in users:"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="false"
android:layout_toLeftOf="@+id/loggedInUserCount"/>
<RadioGroup
android: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">
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Returning user"
android:checked="@{data.isExistingUserChecked}"
android:id="@+id/returningUserRb"/>
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="New user"
android:id="@+id/newUserRb"
/>
</RadioGroup>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/username_block"
android:layout_below="@+id/existingOrNewUser">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Username:"
android:id="@+id/textView"
android:minWidth="100dp"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/username"
android:minWidth="200dp"/>
</LinearLayout>
<LinearLayout
android: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">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Password:"
android:minWidth="100dp"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:ems="10"
android:id="@+id/password"/>
</LinearLayout>
<LinearLayout
android: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}">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Email:"
android:minWidth="100dp"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="textEmailAddress"
android:ems="10"
android:id="@+id/email"/>
</LinearLayout>
<Button
android: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() {
}
@Override
public 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() {
@Override
public void onClick(View v) {
mViewModel.logInClicked();
}
});
}
@Override
public 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 testability
setInitialState();
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() {
@Override
public 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() {
@Override
protected Void doInBackground(Void... params) {
// Simulating some asynchronous task fetching data from a remote server
try {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