DataBinding
1.什么是DataBinding
DataBinding: 是 google 为开发者提供的实用工具套库 Jetpack 中的一个组件库,他是基于APT(Annotation Processing Tool) 实现的一个MVVM框架库。使用 DataBinding 我们可以很轻松的完成M-V 的双向绑定。
// 例如这里与下面的XML就完成了一个双向绑定的过程
public class UserViewModel extends BaseObservable {
private String name = "summer";
public void setName(String name) {
this.name = name
}
@Bindable
public String getName() {
return name;
}
}
<!-- 对应的XML布局 layout_user.xml -->
<layout>
<data>
<variable
name="userViewModel"
type="com.jvup.model.UserViewModel"/>
</data>
<FrameLayout>
<EditText
android:layout_width="300dp"
android:layout_height="39dp"
android:text="@={userViewModel.name}">
</FrameLayout>
</layout>
2.工作原理
DataBinding 是一个基于 APT 技术的MVVM框架,DataBinding 使用了观察这模式来完成双向通知,通过 APT 技术在编译时扫描 XML 布局中的标记生成一个继承自 ViewDataBinding 的子类并写入相关绑定代码,保存在build目录下,不同版本的 android studio 具体路径不一,可以通过切换成android工程目录模式后在java(generated)目录下查找,保存类名为布局名+BindingImpl(如上面的例子layout_user.xml 对应 LayoutUserBindingImpl)。并建立相关绑定事件ID(在BR资源中存放),通过ID可以主动发起相关更新事件通知。(关于详细实现另做源码分析)。ViewDataBinding 内部维护了观察者注册与通知并且持有 所有 View 对象以及绑定注册的model对象。
3.如何使用
1)启动 DataBinding
android studio 老版本
//build.gradle(:app) 那个Module使用就在那个module里写
android {
dataBinding {
enabled = true
}
}
android studio 4.1之后版本
//build.gradle(:app) 那个Module使用就在那个module里写
android {
buildFeatures {
dataBinding = true
}
}
2)具体使用
xml布局:
- 必需使用
<layout>
标记作为起始根标记,解析扫描 xml 时通过识别这个标记来生成对应 ViewDataBinding 类。- 使用
<data>
标记来引入外部绑定数据及类型。<variable>
,<import>
是<data>
子标签.
<variable>
用来引入绑定中使用的变量,如:需要用来做双向绑定的 ViewModel;
<import>
用来引入绑定中使用的类型,如:静态工具类。布局中想使用某个类型必须通过该标记来申明引入,最终引入的类型会被写入到生成的 ViewDataBinding 类引入包中。通过这种方式在生成代码时明确引用类。- 绑定标签属性的 value 必须使用 @{} 包裹,扫描通过识别 @{} 来确定那些属性需要建立绑定关系。@{}只能建立单向绑定,即 Model 数据到 View 的赋值,如果想接受 View 属性值改变,则可以使用@={} 来完成双向绑定;
- 属性匹配规则默认使用 javaBean 约定,通过 getter/setter 方法来匹配,如果 View 提供了 setter 方法(android:adapter 就取 adapter 检查是否存在 setAdapter 方法),匹配属性绑定的赋值方式就会调用这个方法。例如: ListView 提供了 setAdapter(ListAdapter adapter) 方法,那么我们就可以直接在布局中写
android:adapter="@{userViewModel.adapter}"
,匹配成功后会在生成的 ViewDataBinding 类中生成添加listView.setAdapter(userViewModel.adapter);
的相关代码来完成绑定;尽管 android:adapter 并不是基础标准的 android 属性标签,我们也并未对该属性标签做过自定义标签描述。仅仅当 android:adapter 的值为@{}时,这个属性标签被当作 dataBinding 绑定属性时可以这么做。或者说 databinding 并不受标准的xml解析约束。自定义View也一样,因此可以通过提供 setter 方法来减少自定义属性的配置(attrs.xml)- 尽管有上面的默认匹配规则,但是任然还是会有很多属性并非遵循了 javaBean 约定,例如 ImageView 可以通过 android:src="@{userViewModel.head}" 来绑定图片源,但是 ImageView 并没有提供 setSrc() 方法,而设置图片的方法是 setImageDrawable()、setImageURI() 等这些方法;但我们却也可以通过绑定,是因为google为我们提供了许多扩展标记行为的注解,帮助我们完成特殊需求下的匹配绑定,例如 @BindingAdapter 可以扩展绑定行为,当然还有其他更丰富的的注解可以组合完成任何双向绑定的需求和复杂的中间过程。我在另外一篇文章里有描述用法。 Google 还为我们提供了一些针对基础 View 默认的相关注解实现库,可以在 androidx.databinding.adapters 包中找到,当然可以参考学习。
以上最后两条匹配规则都需要注意赋值一定需要与匹配方法的参数类型一致
<layout>
<data>
<variable name="userViewModel" type="com.jvup.model.UserViewModel" />
<import type="android.view.View" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:visibility="@{userViewModel.name == null ? View.GONE : View.VISIBLE}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{userViewModel.name}" />
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@={userViewModel.sex}" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@{userViewModel.head}" />
<ListView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adapter="@{userViewModel.adapter}" />
</FrameLayout>
</layout>
ViewModel规则:
- 属性匹配规则默认遵循 javaBean 约定,提供 getter 方法完成 ViewModel 属性赋值到控件,例如:
userViewModel.getHead()
- 如果需要双向绑定,需提供 setter 方法接受控件属性值更新到 viewmodel 的属性值。完成双向绑定的过程中, getter 方法的返回值类型一定要与 setter 方法的参数值一样,否则会报错。例如:
userViewModel.setSex(String sex) 和 userViewModel.getSex()
- 如果遇到控件值与设置的属性值类型不一致的情况,想完成双向绑定可以借助 DataBinding 框架提供的相关注解来完成。如 @BindingConversion , 相关资料可看注解篇
- 当然直接使用属性也是可以的,直接使用仅只在第一次绑定时赋值,如:
userViewModel.adapter
,之后只能通过 notifyChange() 来完成更新,这个更新会刷新全部绑定属性。- 如果希望可以在 ViewModel 值改变时可以通知 View 控件也改变,则需要标记注解 @Bindable 。在APT扫描阶段会为 @Bindable 描述过的属性方法生成一个 BR.ID 用于注入观察者时使用,这样我们就可以通过这个 BR.ID 来刷新同步数据。这个 BR.ID 的生成规则,就是取变量名或者 getter 方法的get后面的部分,如:userViewModel.name。如果需要完成通知,则 ViewModel 类需要实现 BaseObservable 接口。databinding框架为我们提供了一些默认的基于基础变量类型的绑定属性对象可以直接使用。(有时间整理源码逻辑时整理一份)。在框架绑定通知实现中已经帮我们处理了如果实现了双向绑定,setter方法中写 notifyPropertyChanged 引起的无限循环问题,所以可以不用考虑这个问题。
- 如果使用jetpack 中的 liveData 来完成绑定,则需要使用另外一套观察者模式来接管更新,这里 databinding 的观察者模式不会通知到 livedata , 需要我们在绑定 livedata 时使用具体的 ViewDataBinding 实现来调用
ViewDataBinding.setLifecycleOwner(this);
开启。这局话可以卸载 activity 的onCreate方法里。(具体以后需要整理一份 jetpack ViewModel 资料)- 使用 jetpack 的 liveData 会检查生命周期,而使用 BaseObservable 来完成绑定,赋值时并不会在框架级别检查生命周期,需要自行处理,但是使用 BaseObservable 可以使用方法来代替属性,相对比 liveData 在某些场景中有更简单的效果。
package com.jvup.model;
import android.widget.BaseAdapter;
//未支持到androidx的使用的是这个包名
//import android.databinding.BaseObservable;
import androidx.databinding.BaseObservable;
import com.jvup.model.BR;
public class UserViewModel extends BaseObservable {
// 直接使用 如:android:adapter="@{userViewModel.adapter}"
public final BaseAdapter adapter = new SummerBindAdapter();
// 性别属性
private boolean isMale = true;
// 姓名属性
private String name;
public String headPortrait;
// 遵循 javaBean 约定 如:android:src="@{userViewModel.head}"
public String getHead() {
return headPortrait;
}
// 这个是错误的不能用来接收性别属性
// public void setSex(boolean isMale) {
// this.isMale = isMale;
// }
public void setSex(String sex) {
this.isMale = "男".equal(sex);
}
public String getSex() {
return isMale ? "男" : "女";
}
public void setName(String name) {
this.name = name;
// 用于通知控件更新指定ID的属性。
notifyPropertyChanged(BR.name);
}
@Bindable
public String getName() {
return name;
}
}