Data Binding 系列(六)绑定适配器

1,351 阅读4分钟

绑定适配器的作用就是,调用 UI 框架层合适的方法,给 view 属性进行赋值。

比如,调用 setText() 方法给 TextViewtext 属性赋值;调用 setOnClickListener() 方法,给 viewonClick 属性赋值。

绑定适配器,可以让你决定给 view 属性赋值调用哪个方法,还可以让你自己定制该方法的具体逻辑。

view 属性赋值

当数据发生变化时,绑定类必须调用 view 的合适的 set() 方法,给 view 属性赋值。这时候有三种选择:一种是让 Data Binding 库自己决定调用哪个方法;一种是明确指定调用哪个方法;一种是自己定义一个方法。

  • Data Binding 库自动选择

比如 view 有一个属性叫做 exampleData Binding 库会试图去找一个 setExample(args) 的方法,这个方法接收的参数类型需要和绑定表达式的返回类型一致。注意,属性命名空间是不考虑的,寻找方法的唯一标准就是,方法名和参数类型。

比如,对于表达式 android:text="@{user.name}"Data Binding 库会寻找该 viewsetText(args) 方法,如果 user.name 的类型是 String ,则寻找 setText(String args) 方法;如果 user.name 的类型是 Int ,则寻找 setText(Int args) 方法。

即使一个 viewandroid 命名空间没有对应的属性,Data Binding 库依然可以正常工作。比如,DrawerLayout 并没有 android:scrimColorandroid:drawerListener 这些属性,却有 setScrimColor(int)setDrawerListener(DrawerListener) 方法,这时我们就可以使用自定义命名空间的属性 app:scrimColorapp:drawerListener

<android.support.v4.widget.DrawerLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:scrimColor="@{@color/scrim}"
    app:drawerListener="@{fragment.drawerListener}">
  • 明确指定要调用的方法

有时候 view 的属性名和 set() 方法名并不匹配。比如 ImageView 有一个 android:tint 属性,对应的方法名却是 setImageTintList(ColorStateList) ,这时我们可以使用 BindingMethods 注解,BindingMethods 用于注解一个类,它可以包含多个 BindingMethod 注解,BindingMethod 注解就声明了 view 属性要匹配哪个 set() 方法:

@BindingMethods(value = [
    BindingMethod(
        type = android.widget.ImageView::class,
        attribute = "android:tint",
        method = "setImageTintList")])

上面示例表明了:ImageViewandroid:tint 属性,需要调用 setImageTintList() 方法。

  • 自己定义一个方法

有时候 view 属性需要自定义绑定逻辑。比如 view 属性有 android:paddingLeft 属性却没有 setPaddingLeft(int) 方法,但是有 setPadding(int left, int top, int right, int bottom) 方法,这时候我们需要自定义逻辑。这时,我们使用 BindingAdapter 注解一个 static 方法,如下所示:

@BindingAdapter("android:paddingLeft")
fun setPaddingLeft(view: View, padding: Int) {
    view.setPadding(padding,
                view.getPaddingTop(),
                view.getPaddingRight(),
                view.getPaddingBottom())
}

上面的示例表明了:Viewandroid:paddingLeft 的属性,会调用 viewsetPadding(int left, int top, int right, int bottom)

注意,该方法的参数很重要,第一个参数指明了 view 的类型,第二个参数指明了绑定表达式的返回值是 Int 类型。

如果自定义的适配器和系统的适配器有冲突,自定义的适配器会覆盖系统的。

BindingAdapter 注解可以接收多个参数,如下所示:

@BindingAdapter("imageUrl", "error")
fun loadImage(view: ImageView, url: String, error: Drawable) {
    Picasso.get().load(url).error(error).into(view)
}
<ImageView app:imageUrl="@{venue.imageUrl}" app:error="@{@drawable/venueError}" />

上述示例表明了:view 类型是 ImageView ,有两个属性 imageUrlerror ,这两个属性对应绑定表达式的返回类型分别是 StringDrawable

注意,只有当 ImageView 同时有 imageUrlerror 两个属性时,上述方法才会被调用。如果你希望 ImageView 有任一属性都可以调用,可以声明 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 可能需要属性的旧值,可以这么使用:

@BindingAdapter("android:paddingLeft")
fun setPaddingLeft(view: View, oldPadding: Int, newPadding: Int) {
    if (oldPadding != newPadding) {
        view.setPadding(padding,
                    view.getPaddingTop(),
                    view.getPaddingRight(),
                    view.getPaddingBottom())
    }
}
@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)
        }
    }
}

上述示例中,第二个参数是旧值,第三个参数是新值。

数据类型转换

  • 自动转换

在绑定表达式中,表达式的返回值类型需要与对应的 set() 方法接收的参数类型一致,如果不一致我们需要在绑定表达式中进行转换,例如 android:text=@{user.age}user.age 返回值类型是 IntsetText(String text) 接收的参数类型是 String ,此时我们就需要进行转换:

android:text=@{String.valueOf(user.age)}

上述示例中:调用了 String.valueOf() 方法,把 Int 转换为了 String

  • 自定义转换

有些场景是固定地在两种类型之间进行转换,这时候可以使用自定义转换。比如 viewandroid:background 属性接收 Drawable 类型的数据,但是在绑定表达式中使用的却是 Int 类型,如下所示:

<View
   android:background="@{isError ? @color/red : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

这时候,Int 需要转换为 Drawable ,我们可以使用 BindingConversion 注解一个静态方法,如下所示:

@BindingConversion
fun convertColorToDrawable(color: Int) = ColorDrawable(color)

注意,绑定表达式中的返回值类型必须是固定的,如下所示是错误的:

<View
   android:background="@{isError ? @drawable/error : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

上面示例中,绑定表达式的返回值类型是不确定的,可能是 Drawable 或者 Int ,这种使用方法是不行的。