目录
简介
binding的适配器是在dataBinding的基础上进行扩展。dataBinding主要的作用是在XML布局上进行数据绑定和事件绑定。所以binding得适配器就可以在数据绑定和事件绑定的时候扩展自己的绑定逻辑和绑定方法。
既然是扩展,那么就需要了解dataBinding的机制。
设置属性值
当每一个数据绑定值发生改变时(比如XML中为TextView的android:text属性绑定了user.name值,当user.name发生了变化),生成的绑定类(XML自动生成的对应Binding类。例如aty_main.xml对应的AtyMainBinding类)会在视图中调用对应的setter方法(视图会调用TextView的setText方法)。我们可以在这里进行扩展自己逻辑(todo)。
自动选择方法(使用组件现有的setter方法作为绑定属性)
对于名为 example
的属性,库会自动查找接受兼容类型作为参数的 setExample(arg)
方法。不考虑属性的命名空间。搜索方法时,系统只会使用属性名称和类型。
例如,给定 android:text="@{user.name}"
表达式,库会查找接受user.getName()
返回的类型的 setText(arg)
方法。如果 user.getName()
的返回值类型为 String
,该库会查找接受 String
参数的 setText()
方法。如果该表达式返回 int
,该库会搜索接受 int
参数的 setText()
方法。(表达式必须返回正确类型。您可以根据需要对返回值进行类型转换。)
即使不存在具有给定名称的属性(View的attr,例如:layout_width),数据绑定也会起作用。您可以使用数据绑定为任何 setter 创建属性。例如,支持类 DrawerLayout 没有属性,但有大量 setter。以下布局会自动将 setScrimColor(int) 和 addDrawerListener(DrawerListener) 方法分别用作 app:scrimColor
和 app:drawerListener
属性的 setter:
<android.support.v4.widget.DrawerLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:scrimColor="@{@color/scrim}"
app:drawerListener="@{fragment.drawerListener}">
指定自定义方法名称 (一般用不到,仅供了解)
一些属性具有名称不符的 setter 方法。在这些情况下,可以使用 BindingMethods 注解将属性与 setter 相关联。该注解与类一起使用,可以包含多个 BindingMethod 注解,每个重命名的方法对应一个注解。绑定方法是可添加到应用中任何类的注解。
在以下示例中,android:tint
属性与 setImageTintList(ColorStateList) 方法相关联,而不与 setTint()
方法相关联:
@BindingMethods(value = [
BindingMethod(
type = android.widget.ImageView::class,
attribute = "android:tint",
method = "setImageTintList")])
通常,您无需在 Android 框架类中重命名 setter。属性已经按照命名惯例实现,以便自动查找匹配的方法。
☆提供自定义逻辑
单个属性
一些属性需要自定义绑定逻辑。例如,android:paddingLeft
属性没有关联的 setter。而是提供了 setPadding(left, top, right, bottom)
方法。借助包含 BindingAdapter 注解的静态绑定适配器方法,您可以自定义属性的 setter 的调用方式。
Android 框架类的属性已有 BindingAdapter
注解。以下示例展示了 paddingLeft
属性的绑定适配器:
object BindingAdapters {
/**
* paddingLeft 属性的绑定适配器
* @param view 用于确定与属性关联的视图类型
* @param padding 用于确定给订属性的绑定表达式中接受的类型
*/
@BindingAdapter("android:paddingLeft")
fun setPaddingLeft(view: View, padding: Int) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom())
}
}
绑定适配器也适用于其他类型的自定义。例如,您可以从工作器线程调用自定义加载器来加载图片。
多个属性
您还可以使用接收多个属性的适配器,如以下示例所示:
//创建适配器
object BindingAdapters {
/**
* 为ImageView增加两个自定义属性
* @param view ImageView
* @param url 用与app:imageUrl自定义属性接受的值
* @param error 用与app:error自定义属性接受的值
*/
@BindingAdapter("imageUrl", "error")
fun loadImage(view: ImageView, url: String, error: Drawable) {
Picasso.get().load(url).error(error).into(view)
}
}
//XML中使用适配器
<ImageView app:imageUrl="@{venue.imageUrl}" app:error="@{@drawable/venueError}" />
注意: 数据绑定库在匹配时会忽略自定义命名空间。
可选属性
上面例子,当且仅当ImageView控件被设置imageUrl属性为String类型,error为Drawable类型时loadImage才会调用。如果希望两个属性设置了任何一个就调用loadImage,就需要将适配器requireAll设置为false。如下:
@BindingAdapter(value = ["imageUrl", "placeholder"], requireAll = false)
fun setImageUrl(imageView: ImageView, url: String?, placeHolder: Drawable?) {
if (url == null) {
imageView.setImageDrawable(placeholder);
} else {
MyImageLoader.loadInto(imageView, url, placeholder);
}
}
新旧值对比
绑定适配器方法可以在其处理程序中接受旧值。同时接受旧值和新值的方法必须先为属性声明旧值,再声明新值,如以下示例所示:
@BindingAdapter("android:paddingLeft")
fun setPaddingLeft(view: View, oldPadding: Int, newPadding: Int) {
if (oldPadding != newPadding) {
view.setPadding(newPadding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom())
}
}
事件处理器脚本注意事项
事件处理脚本只能与具有一种抽象方法的接口或抽象类一起使用,如下示例:
因为View.OnLayoutChangeListener只有onLayoutChange这一个抽象方法,所以在XML中可以用
<View android:onLayoutChange="@{() -> handler.layoutChanged()}"/>
来绑定事件处理脚本。
@BindingAdapter("android:onLayoutChange")
fun setOnLayoutChangeListener(
view: View,
oldValue: View.OnLayoutChangeListener?,
newValue: View.OnLayoutChangeListener?
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
if (oldValue != null) {
view.removeOnLayoutChangeListener(oldValue)
}
if (newValue != null) {
view.addOnLayoutChangeListener(newValue)
}
}
}
当监听器具有多个方法时,必须将其拆分为多个监听器,例如,View.OnAttachStateChangeListener 中有两个方法:onViewAttachedToWindow(View) 和 onViewDetachedFromWindow(View)。因此在绑定事件的时候分别 android:onViewDetachedFromWindow 和android:onViewAttachedToWindow这两个事件绑定器来进行监听。
由于更改一个监听器可能会影响另一个监听器,因此您需要一个适用于其中一个属性或同时适用于这两个属性的适配器。您可以在注解中将 requireAll
设置为 false
,以指定并非必须为每个属性分配一个绑定表达式,如以下示例所示:
@BindingAdapter(
"android:onViewDetachedFromWindow",
"android:onViewAttachedToWindow",
requireAll = false
)
fun setListener(view: View, detach: OnViewDetachedFromWindow?, attach: OnViewAttachedToWindow?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
val newListener: View.OnAttachStateChangeListener?
newListener = if (detach == null && attach == null) {
null
} else {
object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View) {
attach.onViewAttachedToWindow(v)
}
override fun onViewDetachedFromWindow(v: View) {
detach.onViewDetachedFromWindow(v)
}
}
}
val oldListener: View.OnAttachStateChangeListener? =
ListenerUtil.trackListener(view, newListener, R.id.onAttachStateChangeListener)
if (oldListener != null) {
view.removeOnAttachStateChangeListener(oldListener)
}
if (newListener != null) {
view.addOnAttachStateChangeListener(newListener)
}
}
}
☆绑定值转换
在某些情况下绑定值不符合绑定属性的需求,需要将绑定值转换为绑定属性需要的类型。例如,视图的android:background属性需要Drawable,但是绑定值为color值为整数。那么就需要自定义一个转换方法:每当属性需要Drawable值,且绑定值为int时,将int转换为ColorDrawable。
示例:
@BindingConversion
fun convertColorToDrawable(color: Int) = ColorDrawable(color)
注意:转换方法必须为静态方法。
使用场景:
<View
android:background="@{isError ? @color/red : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
注意:绑定表达式中提供的值类型必须一致。不能同一表达式中使用不同的类型,如下错误示例。
<!-- @drawable 和 @color 为不同类型数据,编译会报错.-->
<View
android:background="@{isError ? @drawable/error : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
完。
以上为个人参考Android官网整理的基本用法,可能有疏漏,仅供参考。