MVVM实现数据双向绑定

Associate what you’ re trying to learn with what you already know. —— Daily English

这篇文章旨在通过一个Demo让我们对Android中的MVVM架构进行初步的认识。

MVVM与DataBinding的关系

很多同学会将这两者混为一谈,所以开始介绍之前,我们需要先理清楚这两者的关系。
MVVM和MVC、MVP一样,是项目中的架构设计思想;DataBinding是一种工具,它可以用于MVVM,也同样可以用于MVC和MVP。所以这两者是两回事,一个是架构设计思想,一个是工具。但是有一点,那就是Android中的MVVM一般都需要借助DataBinding来实现,这也是很多人将这两者混为一谈的原因。

MVVM简介

MVVM是更节省的设计模式,能实现双向的数据绑定。

须知

MVVM可以理解成M V VM。其中的M指的是Model层,也就是我们的JavaBean。V指的是VIew层,也就是我们具体的布局,如EditText等。VM指的是ViewModel层,它是Model层和View层的一个桥梁,也用来处理视图逻辑和业务逻辑。
简而言之,M还是Model,V还是View,VM就是ViewModel层。三者的关系大致如下图所示:
在这里插入图片描述
这个架构模式有如下两个特点

  1. 降低耦合:一个ViewModel层可以绑定不同的View层,当Model变化时View可以不变。
  2. 可重用性:可以把一些视图逻辑放在ViewModel层中,让很多View重用这些视图逻辑。

ViewModel相当于model层和View层的一个桥梁,当View层比如说一个EditText的值发生改变了,无需通过Activity,就直接可以改变JavaBean对应的属性值。Model层set一个值,也无需通过Activity,就可以直接改变页面上的值。

MVVM是有弊端的,一个是修改之后需要经常ReBuild,而且项目越大,ReBuild的时间也越长。另外也有三个原因会导致它的内存消耗比较大,这个会在介绍DataBinding的时候讲到(点击查看)。这也是有些公司不愿意用MVVM架构的原因。但是,MVVM为什么还会这么火呢,就是因为这种View和Model的双向绑定思想是值得我们学习的,也很可能是一种趋势。

什么是单向数据绑定,什么是双向数据绑定。

单向绑定是指View层(如EditText)上的数据改变会实时更新到Model层JavaBean中对应的属性值(如username)上。或者,Model层的数据改变会实时更新到View层上的显示,这样我们称之为单向的数据绑定。而双向绑定呢,是指Model层和View层,无论那层数据改变都会实时更新到对方,Model层数据改变会更新View,同样,View层数据改变会更新Model,这样就称之为双向的数据绑定。

项目实践

模拟一个登录的功能。

第一步,引入
在module的build.gradle文件中引入DataBinding

android {
	...
	// 添加DataBinding依赖
	dataBinding{
	    enabled = true
	}
)

第二步,定义实体类

public class UserInfo {
    // 被观察的属性(切记:必须是public修饰符,因为是DataBinding的规范)
    public ObservableField<String> name = new ObservableField<>();

    public ObservableField<String> pwd = new ObservableField<>();
}

实体类也可以定义成原始的那种格式,添加get(),set()方法,也可以定义成被观察者属性的格式。只要注意两点,一个是被观察者属性,一个是有刷新属性的方法。这里就不做解释,对格式有疑问的同学请参考我另一篇讲DataBinding的文章(点击查看)。

第三步,定义布局

布局界面很简单
在这里插入图片描述
对应xml文件代码如下

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <!--定义该布局需要绑定的数据名称和类型-->
    <data>
        <variable
            name="loginViewModel"
            type="pers.owen.my_mvvm.vm.LoginViewModel" />
    </data>

    <!-- 下部分内容和平时布局文件一样 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="20dp"
            android:hint="请输入账户"
            android:text="@={loginViewModel.userInfo.name}" />

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="20dp"
            android:hint="请输入密码"
            android:text="@={loginViewModel.userInfo.pwd}" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:onClick="@{loginViewModel.loginClickListener}"
            android:text="登录" />

    </LinearLayout>
</layout>

布局格式参考上述XML即可。

这里我们发现<data>中的variable是一个ViewModel的名称和具体路径,当然也可以直接是一个实体类。这也就是我们为什么说DataBinding其实也可以用在MVC或MVP架构当中,因为这篇文章讲的是MVVM的架构,所以这里定义一个专门处理登录逻辑的ViewModel,叫做LoginViewModel类。

其中如android:onClick="@{loginViewModel.loginClickListener}"loginViewModel就是<data>标签下LoginViewModel的name。可以把这里的loginViewModel理解成一个对象,而loginViewModel.loginClickListener就是相当于调用这个对象中一个叫loginClickListener的public方法。

LoginViewModel的方法随即附上。

第四步,定义ViewModel

public class LoginViewModel {
    public UserInfo userInfo;

    public View.OnClickListener loginClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // 尝试1:view --> model单向绑定测试,改变EditText的值,查看bean中的对应属性值是否发生变化。
//            Log.e("owen >>> ", userInfo.name.get() + "--" + userInfo.pwd.get());

            // 尝试2:model --> view单向绑定测试,Model层属性的变更,也会改变View层的显示
//            userInfo.name.set("Owen");
//            userInfo.pwd.set("0410");

            // 尝试3:模拟网络请求
            new Thread(new Runnable() {
                @Override
                public void run() {
                    if ("Owen".equals(userInfo.name.get()) && "123".equals(userInfo.pwd.get())) {
                        Log.e("Owen >>> ", "登录成功!");
                    } else {
                        Log.e("Owen >>> ", "登录失败!");
                    }
                }
            }).start();
        }
    };
}

注意LoginViewModel类中的属性名userInfologinClickListener是要和xml布局中如android:onClick="@{loginViewModel.loginClickListener}"的名称对应上。可以通过ctrl+左键点击跳转验证。

第五步,Rebuild Project
Rebuild完成后,会在data_binding_base_class_source_out目录下生成以[布局名]Binding.java文件,这里我们的布局叫activity_main,所以生成了一个名为ActivityMainBinding的Java类文件。目录的具体位置如下图所示:
在这里插入图片描述
有了它,我们就可以做绑定操作了。

最后一步,书写代码绑定

在Activity中创建ActivityMainBinding对象

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 1、必须先ReBuilder,2、书写代码绑定
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

        new LoginViewModel(binding);
    }
}

通过构造方法传入LoginViewModel进行绑定

 public LoginViewModel(ActivityMainBinding binding) {
     userInfo = new UserInfo();
     // 将ViewModel和View进行绑定,通过DataBinding工具。
     binding.setLoginViewModel(this);
 }

最后LoginViewModel的完整代码如下:

public class LoginViewModel {
    public UserInfo userInfo;

    public LoginViewModel(ActivityMainBinding binding) {
        userInfo = new UserInfo();
        // 将ViewModel和View进行绑定,通过DataBinding工具。
        binding.setLoginViewModel(this);
    }

    public View.OnClickListener loginClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // 尝试1:view --> model单向绑定测试,改变EditText的值,查看bean中的对应属性值是否发生变化。
//            Log.e("owen >>> ", userInfo.name.get() + "--" + userInfo.pwd.get());

            // 尝试2:model --> view单向绑定测试,Model层属性的变更,也会改变View层的显示
//            userInfo.name.set("Owen");
//            userInfo.pwd.set("0410");

            // 尝试3:模拟网络请求
            new Thread(new Runnable() {
                @Override
                public void run() {
                    if ("Owen".equals(userInfo.name.get()) && "123".equals(userInfo.pwd.get())) {
                        Log.e("Owen >>> ", "登录成功!");
                    } else {
                        Log.e("Owen >>> ", "登录失败!");
                    }
                }
            }).start();
        }
    };
}

尝试代码依次开启,做测试
尝试1:在界面上输入用户名,密码,点击登录,查看log发现UserInfo中的两个属性值已经被改变。证明View --> Mode单向绑定是Ok的。
尝试2:程序运行后,直接点击登录,发现界面上的EditText上的值已经更改。证明Model --> View的单向绑定也是成功的。
尝试3:模拟网络登录。

这就是我们在业务中用到的MVVM,我们发现在Activity中什么事情都不用干,但是我们必须有一个VM来作为V和M的桥梁来沟通。我们现在不需要在Activity中做很多复杂的东西,这就是架构MVVM的思想。你学到了吗?


文中Demo下载地址

Android 架构设计模式系列文章索引

MVC架构设计与经典的三层模型

MVVM实现数据双向绑定

DataBinding的使用与原理


本系列文章引导页点击这里

猜你喜欢

转载自blog.csdn.net/u014158743/article/details/105462190