MVX Android设计架构浅析-MVVM

来源:互联网 发布:矩阵秩为1和迹的关系 编辑:程序博客网 时间:2024/06/05 15:22

简介

读过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风头正劲,不过还没有十分红火,原因是Data-binding还没有完美实现,Android仅支持单向数据绑定也就是从Module到View,而另外一个方向从View到Module需要手动去实现。

简单交互图

我们将上面的图示经过精简一下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设计架构浅析
MVX Android设计架构浅析-MVC
MVX Android设计架构浅析-MVP
MVX Android设计架构浅析-MVVM

0 0
原创粉丝点击