BindingAdapter
1.什么是 BindingAdapter
BindingAdapter :绑定适配器,是 Jetpack DataBinding 中用来扩展布局 xml 属性行为的注解,允许你针对布局 xml 中的一个或多个属性进行绑定行为扩展,这个属性可以是自定义属性,也可以是原生属性。这个扩展行为可以是简单的ViewModel属性与控件赋值绑定,也可以是关联某个控件属性的额外操作,例如在设置属性之前进行值域检查,或类型转换,或者统一处理一些事情。
例:
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@{"http://****/**.jpg"}" />
上例中:src是默认的控件属性,而src需要使用一个资源ID,来完成赋值,并不支持网络地址来获取设置图片,此时就可以通过 BindingAdapter 来扩展行为,使其具备使用一个网络地址,并且从网络下载图片并显示的能力。
@BindingAdapter("android:src")
public static void setSrc(ImageView view, String bitmapPath) {
if(ObjectUtil.nonNull(bitmapPath)) {
GlideApp.with(view.getContext())
.load(bitmapPath)
.into(view);
} else {
view.setImageDrawable(null);
}
}
这样就完成了对于 android:src 布局属性的行为扩展。此时我们可以直接设置地址路径来完成网络下载图片并复制给 ImageView 的能力了。
2.工作机制
BindingAdapter 是基于 APT 注解技术工作的,APT 可以在项目构建时更具相关编写规则生成特定代码完成一些指定功能的技术。
BindingAdapter 有两个属性,value 是一个 String[] ,requireAll 是一个boolean类型:
value 用来描述 XML 中感兴趣的关联属性,这里是个数组,说明一个扩展方法可以同时关注多个 XML 属性。这个很重要,有时候我们可能在对控件进行赋值时,需要同时给定多个值,而 XML 属性值却一次只能指定一个变量,这样可能会让我们不的不为此去扩展一个复合对象来完成这样的赋值,但是实际上并不需要那样。
requireAll 用来对 value 补充说明的,这个值默认是 true,表示使用这个适配规则必须在 XML 中声明该注解关注所有属性值,否则编译时会报错,而 false 则不需要,他允许只使用关注的部分或全部属性来使用该规则。
例: 当requireAll = true 时
@BindingAdapter(value = {"galleryEffectWidthLeft", "galleryEffectWidthRight", "galleryEffectPageMargin", "galleryEffectScale"}, requireAll = true)
public static void setBannerGalleryEffect(Banner banner, int galleryWidthLeft, int galleryWidthRight, int galleryPageMargin, float galleryScale) {
banner.setBannerGalleryEffect(galleryWidthLeft, galleryWidthRight, galleryPageMargin, galleryScale);
}
//正确 galleryEffect 相关4个熟悉都设置了
<com.youth.banner.Banner
android:layout_width="match_parent"
android:layout_height="175dp"
android:layout_marginTop="90dp"
app:adapter="@{recommendVipZoneViewModel.bannerAdapter}"
app:banner_radius="8dp"
app:galleryEffectWidthLeft="@{7}"
app:galleryEffectWidthRight="@{8}"
app:galleryEffectPageMargin="@{9}"
app:galleryEffectScale="@{0.85f}" />
//错误,app:galleryEffectScale 属性未设置情况
<com.youth.banner.Banner
android:layout_width="match_parent"
android:layout_height="175dp"
android:layout_marginTop="90dp"
app:adapter="@{recommendVipZoneViewModel.bannerAdapter}"
app:banner_radius="8dp"
app:galleryEffectWidthLeft="@{7}"
app:galleryEffectWidthRight="@{8}"
app:galleryEffectPageMargin="@{9}" />
例: 当requireAll = false 时
@BindingAdapter(value = {"galleryEffectWidthLeft", "galleryEffectWidthRight", "galleryEffectPageMargin", "galleryEffectScale"}, requireAll = false)
public static void setBannerGalleryEffect(Banner banner, int galleryWidthLeft, int galleryWidthRight, int galleryPageMargin, float galleryScale) {
banner.setBannerGalleryEffect(galleryWidthLeft, galleryWidthRight, galleryPageMargin, galleryScale);
}
//正确,需要注意的是,此时少了的galleryEffectScale属性可能会接受一个默认只比如这里float可能为0.0
<com.youth.banner.Banner
android:layout_width="match_parent"
android:layout_height="175dp"
android:layout_marginTop="90dp"
app:adapter="@{recommendVipZoneViewModel.bannerAdapter}"
app:banner_radius="8dp"
app:galleryEffectWidthLeft="@{7}"
app:galleryEffectWidthRight="@{8}"
app:galleryEffectPageMargin="@{9}" />
3.如何使用
关于 BindingAdapter 注解本身已经有所了解。如何使用
首先,关于使用 BindingAdapter 描述的函数签名,参数列表必须按照以下固定形式出现:
// 类构建可以随意,APT会在构建时扫描全局代码
public class ViewAttrAdapter {
// 需要注意的是 XML标签 关注了几个,参数列表就需要写几个对应的接受参数。且关注控件类必须在第一个参数。
@BindingAdapter({xml属性标签, ...})
public static void 函数名(关注的控件类 view, xml属性标签值 value, ...){
// 行为
}
// 可以包含多个函数
.....
}
而在XML布局中,我们应该使用 BindingAdapter 标签对应的标签类型进行赋值,切必须使用@{}作为值包裹,这是为了提供给APT解析XML是区别普通赋值的方式。
<View
android:layout_width="match_parent"
android:layout_height="175dp"
app:xml属性标签="@{函数参数接受类型的值}" />
需要注意的是绑定解析的触发规则:
// 这个限定使用时一定是android:src才能匹配
@BindingAdapter("android:src")
// 这个限定使用时 app:src 和 android:src 都可以匹配,但需要注意的是,android必须是在自定义属性声明的XML中有描述过的,如 attrs.xml 中
@BindingAdapter("src")
触发解析的属性必须满足 标签="@{value}" 这里的 @{} 一定不能丢。
最后总结:BindingAdapter 提供了一个高自由的切片编程能力,允许在xml解析是绑定扩展行为,可以帮助完成很多事情,如事件监听,属性赋值,类型转换,统一业务处理,比如埋点处理,防重按等等。而 BindingAdapter 仅仅是Databinding中一个使用较多的注解,之后会学习更多相关注解帮助扩展双向绑定,双向赋值之类的事情。(一上属于个人理解,如有不同想法,可以沟通修改,也会不时更新)