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层。三者的关系大致如下图所示:
这个架构模式有如下两个特点
- 降低耦合:一个ViewModel层可以绑定不同的View层,当Model变化时View可以不变。
- 可重用性:可以把一些视图逻辑放在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
类中的属性名userInfo
和loginClickListener
是要和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 架构设计模式系列文章索引
上一篇: Vue双向数据绑定之原理及实现3
下一篇: 三分钟手操rpm包