DataBinding: 是 google 为开发者提供的实用工具套库 Jetpack 中的一个组件库,他是基于APT(Annotation Processing Tool) 实现的一个MVVM框架库。使用 DataBinding 咱们能够很轻松的完成M-V 的双向绑定。html
// 例如这里与下面的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>
DataBinding 是一个基于 APT 技术的MVVM框架,DataBinding 使用了观察这模式来完成双向通知,经过 APT 技术在编译时扫描 XML 布局中的标记生成一个继承自 ViewDataBinding 的子类并写入相关绑定代码,保存在build目录下,不一样版本的 android studio 具体路径不一,能够经过切换成android工程目录模式后在java(generated)目录下查找,保存类名为布局名+BindingImpl(如上面的例子layout_user.xml 对应 LayoutUserBindingImpl)。并创建相关绑定事件ID(在BR资源中存放),经过ID能够主动发起相关更新事件通知。(关于详细实现另作源码分析)。ViewDataBinding 内部维护了观察者注册与通知而且持有 全部 View 对象以及绑定注册的model对象。
java
android studio 老版本android
//build.gradle(:app) 那个Module使用就在那个module里写 android { dataBinding { enabled = true } }
android studio 4.1以后版本app
//build.gradle(:app) 那个Module使用就在那个module里写 android { buildFeatures { dataBinding = true } }
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; } }