什么是Data Binding
DataBinding是一个实现数据和UI绑定的框架,是实现MVVM模式的工具,而MVVM中的VM(ViewModel)和View可以通过DataBinding来实现数据绑定(目前已支持双向绑定)。
能做什么?
- XML中实现控件绑定,能实现简单逻辑,减少view id 的定义,数据绑定直接发生在xml
- 简化Activity && Fragment 内的UI操作代码.
使用场景
data binding的意义主要数据的变动可以自动触发UI界面的刷新。但是如果我们使用的是传统的java bean对象的时候,是没有办法实现“数据变更触发ui界面”的目的的。而 Bindable 注解就是帮助我们完成这个任务的。
如果我们要实现“数据变更触发ui界面”的话,途径主要有两个:
- 继承 BaseObservable ,使用 Bindable 注解field的getter并且在调用setter的使用使用 OnPropertyChangedCallback#onPropertyChanged
- 使用data-binding library当中提供的诸如 ObservableField<>, ObservableInt作为属性值
优势
UI代码放到了xml中,布局和数据更紧密
性能超过手写代码
保证执行在主线程
劣势
IDE支持还不那么完善(提示、表达式)
报错信息不那么直接
重构支持不好(xml中进行重构,java代码不会自动修改)
使用
1 | android { |
layout
布局根节点必须是
1 | <layout> |
在xml的最外层套上layout标签即可,修改后就可以看到生成了该布局对应的*Binding类。
data
标签的内容即DataBinding的数据. data标签只能存在一个.
layout tag
把一个普通的layout变成data binding layout也只要几行的修改:
variable
通过
1 | <data> |
通过DataBinding的setxx()方法可以给Variable设置数据. name值不能包含_下划线
import
第二种写法(导入), 默认导入了java/lang包下的类(String/Integer). 可以直接使用被导入的类的静态方法.
1 | <data> |
class
标签有个属性
1 | <!--自定义类名--> |
Binding生成规则
默认生成规则:xml通过文件名生成,使用下划线分割大小写。
比如activity_demo.xml,则会生成ActivityDemoBinding,item_search_hotel则会生成ItemSearchHotelBinding。
view的生成规则类似,只是由于是类变量,首字母不是大写,比如有一个TextView的id是first_name,则会生成名为firstName的TextView。
我们也可以自定义生成的class名字,只需要:
1 | <data class=“ContactItem”> |
表达式
算术 + - / * %
字符串合并 +
逻辑 && ||
二元 & | ^
一元 + - ! ~
移位 >> >>> <<
比较 == > < >= <=
Instanceof
Grouping ()
文字 - character, String, numeric, null
Cast
方法调用
Field 访问
Array 访问 []
三元 ?:
空合并运算符: android:text=“@{user.displayName ?? user.lastName}”
android:marginLeft=”@{@dimen/margin + @dimen/avatar_size}”
组合字符串
android:text=”@{@string/nameFormat(firstName, lastName)}”
避免空指针
data binding会自动帮助我们进行空指针的避免,比如说@{employee.firstName},如果employee是null的话,employee.firstName则会被赋默认值(null)。int的话,则是0。
需要注意的是数组的越界,毕竟这儿是xml而不是java,没地方让你去判断size的。
include
对于include的布局,使用方法类似,不过需要在里面绑定两次,外面include该布局的layout使用bind:user给set进去。
这里需要注意的一点是,被include的布局必须顶层是一个ViewGroup,目前Data Binding的实现,如果该布局顶层是一个View,而不是ViewGroup的话,binding的下标会冲突(被覆盖),从而产生一些预料外的结果。
引入的页面怎样加载点击事件
1 | <variable |
diy_back_title.xml
1 | <variable name="clickListener" type="android.view.View.OnClickListener" /> |
1 | binding.setClickListener(this); |
ViewStubs
ViewStub比较特殊,在被实际inflate前是不可见的,所以使用了特殊的方案,用了final的ViewStubProxy来代表它,并监听了ViewStub.OnInflateListener:
1 | private OnInflateListener mProxyListener = new OnInflateListener() { |
在onInflate的时候才会进行真正的初始化。
Observable Fields
如果所有要绑定的都需要创建Observable类,那也太麻烦了。所以Data Binding还提供了一系列Observable,包括 ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, 和ObservableParcelable。我们还能通过ObservableField泛型来申明其他类型,如:
1 | private static class User { |
而在xml中,使用方法和普通的String,int一样,只是会自动刷新,但在java中访问则会相对麻烦:
1 | user.firstName.set("Google"); |
相对来说,每次要get/set还是挺麻烦,私以为还不如直接去继承BaseObservable。
Observable Collections
有一些应用使用更动态的结构来保存数据,这时候我们会希望使用Map来存储数据结构。Observable提供了ObservableArrayMap:
1 | ObservableArrayMap<String, Object> user = new ObservableArrayMap<>(); |
动态变量
有时候,我们并不知道具体生成的binding类是什么。比如在RecyclerView中,可能有多种ViewHolder,而我们拿到的holder只是一个基类(这个基类具体怎么写下篇中会提到),这时候,我们可以在这些item的layout中都定义名字同样的variable,比如item,然后直接调用setVariable:
1 | public void onBindViewHolder(BindingHolder holder, int position) { |
executePendingBindings会强制立即刷新绑定的改变。
表达式链
重复的表达式
1 | <ImageView android:visibility=“@{user.isAdult ? View.VISIBLE : View.GONE}”/> |
可以简化为:
1 | <ImageView android:id=“@+id/avatar” |
隐式更新
1 | <CheckBox android:id=”@+id/seeAds“/> |
这样CheckBox的状态变更后ImageView会自动改变visibility。
Lambda表达式
除了直接使用方法引用,在Presenter中写和OnClickListener一样参数的方法,我们还能使用Lambda表达式:
1 | android:onClick=“@{(view)->presenter.save(view, item)}” |
我们还可以在lambda表达式引用view id(像上面表达式链那样),以及context。
使用建议
- 尽量在项目中进行尝试,只有在不断碰到业务的需求时,才会在真正的场景下使用并发现Data Binding的强大之处。
- 摸索xml和java的界限,不要以为Data Binding是万能的,而想尽办法把逻辑写在xml中,如果你的同事没法一眼看出这个表达式是做什么的,那可能它就应该放在Java代码中,以ViewModel的形式去承担部分逻辑。
- Lambda表达式/测试时注入等Data Binding的高级功能也可以自己多试试,尤其是注入,相当强大。
- callback绑定只做事件传递,NO业务逻辑
- 保持表达式简单(不要做过于复杂的字符串、函数调用操作)
参考资料: