承接上篇,本篇继续讲解一些Data Binding更加进阶的内容,包括:列表绑定、自定义属性、双向绑定、表达式链、Lambda表达式、动画、Component注入(测试)等。
Demo源码库:DataBindingSample。
列表绑定
App中经常用到列表展示,Data Binding在列表中一样可以扮演重要的作用,直接绑定数据和事件到每一个列表的item。
RecyclerView
过去我们往往会使用ListView、GridView、或者GitHub上一些自定义的View来做瀑布流。自从RecyclerView出现后,我们有了新选择,只需要使用LayoutManager就可以。RecyclerView内置的垃圾回收,ViewHolder、ItemDecoration装饰器机制都让我们可以毫不犹豫地替换掉原来的ListView和GridView。
所以本篇仅拿RecyclerView做例子。
Generic Binding
我们只需要定义一个基类ViewHolder,就可以方便地使用上Data Binding:
public class BindingViewHolder<T extends ViewDataBinding> extends RecyclerView.ViewHolder {
protected final T mBinding;
public BindingViewHolder(T binding) {
super(binding.getRoot());
mBinding = binding;
}
public T getBinding() {
return mBinding;
}
}
Adapter可以直接使用该ViewHolder,或者再继承该ViewHolder,T使用具体Item的Binding类(以便直接访问内部的View)。至于Listener,可以在onBindViewHolder
中进行绑定,做法类似于普通View,不做赘述。
由于同一个adapter未必只有一种ViewHolder,可能有好几种View type,所以在onBindViewHolder
中,我们只能获取基类的ViewHolder类型,也就是BindingViewHolder
,所以无法去做具体的set操作,如setEmployee。这时候就可以使用setVariable
接口,然后通过BR来指定variable的name。
又比如我们可能有多重view type对应的xml,可以将对应的variable name全都写为item,这样可以避免强制转换Binding类去做set操作。类似地,监听器也能都统一取名为listener或者presenter。
开源方案及其局限性
evant / binding-collection-adapter
radzio / android-data-binding-recyclerview
均提供了简化的RV data binding方案。
前者可以直接在layout的RV上,设置对应的items和itemView进去,也支持多种view type,还能直接设定对应的LayoutManager。
后者类似地,提供了xml中直接绑定RV的items和itemView的功能。
相比来说前者的功能更强大一些。但这些开源库对应地都丧失了灵活性,ViewModel需要遵循规范,事件的绑定也比较死板,不如自己继承Adapter来得强大。唯一的好处也就是可以少写点代码了。
自定义属性
默认的android命名空间下,我们会发现并不是所有的属性都能直接通过data binding进行设置,比如margin,padding,还有自定义View的各种属性。
遇到这些属性,我们就需要自己去定义它们的绑定方法。
Setter
就像Data Binding会自动去查找get方法一下,在遇到属性绑定的时候,它也会去自动寻找对应的set方法。
拿DrawerLayout举一个例子:
<android.support.v4.widget.DrawerLayout
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
app:scrimColor=“@{@color/scrimColor}”/>
如此,通过使用app命名空间,data binding就会去根据属性名字找对应的set方法,scrimColor -> setScrimColor:
public void setScrimColor(@ColorInt int color) {
mScrimColor = color;
invalidate();
}
如果找不到的话,就会在编译期报错。
利用这种特性,对一些第三方的自定义View,我们就可以继承它,来加上我们的set函数,以对其使用data binding。
比如Fresco的SimpleDraweeView
,我们想要直接在xml指定url,就可以加上:
public void setUrl(String url) {
view.setImageURI(TextUtils.isEmpty(url) ? null : Uri.parse(url));
}
这般,就能直接在xml中去绑定图片的url。这样是不是会比较麻烦呢,而且有一些系统的View,难道还要继承它们然后用自己实现的类?其实不然,我们还有其他方法可以做到自定义属性绑定。
BindingMethods
如果View本身就支持这种属性的set,只是xml中的属性名字和java代码中的方法名不相同呢?难道就为了这个,我们还得去继承View,使代码产生冗余?
当然没有这么笨,这时候我们可以使用BindingMethods注释。
android:tint是给ImageView加上着色的属性,可以在不换图的前提下改变图标的颜色。如果我们直接对android:tint使用data binding,由于会去查找setTint方法,而该方法不存在,则会编译出错。而实际对应的方法,应该是setImageTintList
。
这时候我们就可以使用BindingMethod指定属性的绑定方法:
@BindingMethods({
@BindingMethod(type = “android.widget.ImageView”,
attribute = “android:tint”,
method = “setImageTintList”),
})
我们也可以称BindingMethod为Setter重命名。
BindingAdapter
如果没有对应的set方法,或者方法签名不同怎么办?BindingAdapter注释可以帮我们来做这个。
比如View的android:paddingLeft属性,是没有对应