绑定适配器

绑定适配器负责对 设置值。例如,设置属性值,就像调用 setText() 方法。另一个 设置事件监听器,比如调用 setOnClickListener() 方法。

借助数据绑定库,您可以指定要调用的用于设置值的方法, 提供自己的绑定逻辑,并通过以下方式指定所返回对象的类型: 使用适配器

设置属性值

每当绑定值发生变化时,生成的绑定类都必须调用 setter 方法。您可以将数据绑定 库会自动确定此方法,您也可以显式声明 方法或提供自定义逻辑来选择方法。

自动选择方法

对于名为 example 的属性,该库会自动查找该方法 接受兼容类型作为参数的 setExample(arg)。命名空间 属性。仅使用属性名称和类型 。

例如,给定 android:text="@{user.name}" 表达式,该库 查找接受由 返回的类型的 setText(arg) 方法 user.getName()。如果 user.getName() 的返回类型为 String,则 库会查找接受 String 参数的 setText() 方法。如果 表达式返回一个 int,该库会搜索符合以下条件的 setText() 方法: 接受 int 参数。表达式必须返回正确的类型。您可以 必要时对返回值进行类型转换。

即使不存在具有给定名称的特性,数据绑定也会起作用。您可以 使用数据绑定为任何 setter 创建属性。例如,支持 类别 DrawerLayout 没有属性,但有很多 setter。以下布局 系统会自动使用 setScrimColor(int)addDrawerListener(DrawerListener) 方法用作 app:scrimColorapp:drawerListener 的 setter 属性:

<androidx.drawerlayout.widget.DrawerLayout
   
android:layout_width="wrap_content"
   
android:layout_height="wrap_content"
   
app:scrimColor="@{@color/scrim}"
   
app:drawerListener="@{fragment.drawerListener}">

指定自定义方法名称

一些属性具有名称不符的 setter 方法。在这种情况下, 属性可以使用 BindingMethods 注解。注解与类一起使用,并且可以包含多个 BindingMethod 注解,每个重命名的方法对应一个注解。绑定方法是 您可以将其添加到应用中的任何类。

在以下示例中,android:tint 属性与 setImageTintList(ColorStateList) 方法,而不是使用 setTint() 方法:

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

@BindingMethods({
       
@BindingMethod(type = "android.widget.ImageView",
                      attribute
= "android:tint",
                      method
= "setImageTintList"),
})

通常,您无需在 Android 框架类中重命名 setter。通过 属性已经使用命名惯例实现,以自动 查找匹配方法。

提供自定义逻辑

一些属性需要自定义绑定逻辑。例如,没有 android:paddingLeft 属性的 setter 方法。而是提供了 setPadding(left, top, right, bottom) 方法。具有如下参数的静态绑定适配器方法: BindingAdapter 注解可让您自定义属性 setter 的调用方式。

Android 框架类的属性已有 BindingAdapter 注释。以下示例显示了 paddingLeft 属性:

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

@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
  view
.setPadding(padding,
                  view
.getPaddingTop(),
                  view
.getPaddingRight(),
                  view
.getPaddingBottom());
}

参数类型非常重要。第一个参数决定 与属性关联的视图。第二个参数决定了 指定属性的绑定表达式中接受的类型。

绑定适配器也可用于其他类型的自定义。例如: 可以从工作器线程调用自定义加载器来加载图片。

您还可以使用可接收多个属性的适配器,如 示例:

KotlinJava
@BindingAdapter("imageUrl", "error")
fun loadImage(view: ImageView, url: String, error: Drawable) {
   
Picasso.get().load(url).error(error).into(view)
}

@BindingAdapter({"imageUrl", "error"})
public static void loadImage(ImageView view, String url, Drawable error) {
 
Picasso.get().load(url).error(error).into(view);
}

您可以在布局中使用适配器,如以下示例所示。注意事项 @drawable/venueError 引用应用中的资源。将 设置为 @{} 的资源使其成为有效的绑定表达式。

<ImageView app:imageUrl="@{venue.imageUrl}" app:error="@{@drawable/venueError}" />

如果将 imageUrlerror 用于 ImageView 对象,imageUrl 是一个 字符串,error 是一个 Drawable。如果您想 设置任意属性时要调用的适配器,将可选属性 requireAll 标志设置为 false,如以下示例所示:

KotlinJava
@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(value={"imageUrl", "placeholder"}, requireAll=false)
public static void setImageUrl(ImageView imageView, String url, Drawable placeHolder) {
 
if (url == null) {
    imageView
.setImageDrawable(placeholder);
 
} else {
   
MyImageLoader.loadInto(imageView, url, placeholder);
 
}
}

绑定适配器方法可以在其处理程序中使用旧值。方法 采用旧值和新值时,必须先声明属性的所有旧值, 后跟新值,如以下示例所示:

KotlinJava
@BindingAdapter("android:paddingLeft")
fun setPaddingLeft(view: View, oldPadding: Int, newPadding: Int) {
   
if (oldPadding != newPadding) {
        view
.setPadding(newPadding,
                    view
.getPaddingTop(),
                    view
.getPaddingRight(),
                    view
.getPaddingBottom())
   
}
}

@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int oldPadding, int newPadding) {
 
if (oldPadding != newPadding) {
      view
.setPadding(newPadding,
                      view
.getPaddingTop(),
                      view
.getPaddingRight(),
                      view
.getPaddingBottom());
   
}
}

事件处理脚本只能与接口或抽象类(具有 抽象方法,如以下示例所示:

KotlinJava
@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)
       
}
   
}
}
@BindingAdapter("android:onLayoutChange")
public static void setOnLayoutChangeListener(View view, View.OnLayoutChangeListener oldValue,
       
View.OnLayoutChangeListener newValue) {
 
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
   
if (oldValue != null) {
      view
.removeOnLayoutChangeListener(oldValue);
   
}
   
if (newValue != null) {
      view
.addOnLayoutChangeListener(newValue);
   
}
 
}
}

按如下方式在布局中使用此事件处理脚本:

<View android:onLayoutChange="@{() -> handler.layoutChanged()}"/>

如果一个监听器有多个方法,则必须拆分成多个监听器。 例如: View.OnAttachStateChangeListener 有两种方法: onViewAttachedToWindow(View)onViewDetachedFromWindow(View)。 该库提供了两个接口,用于区分属性和处理程序 :

KotlinJava
// Translation from provided interfaces in Java:
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
interface OnViewDetachedFromWindow {
   
fun onViewDetachedFromWindow(v: View)
}

@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
interface OnViewAttachedToWindow {
   
fun onViewAttachedToWindow(v: View)
}
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewDetachedFromWindow {
 
void onViewDetachedFromWindow(View v);
}

@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewAttachedToWindow {
 
void onViewAttachedToWindow(View v);
}

因为更改一个监听器会影响另一个监听器,所以需要一个 对这两个属性都适用。您可以在以下位置将 requireAll 设置为 false: 注解,以指定并非必须为每个属性分配绑定 表达式,如以下示例所示:

KotlinJava
@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)
       
}
   
}
}
@BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"}, requireAll=false)
public static void setListener(View view, OnViewDetachedFromWindow detach, OnViewAttachedToWindow attach) {
   
if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) {
       
OnAttachStateChangeListener newListener;
       
if (detach == null && attach == null) {
            newListener
= null;
       
} else {
            newListener
= new OnAttachStateChangeListener() {
               
@Override
               
public void onViewAttachedToWindow(View v) {
                   
if (attach != null) {
                        attach
.onViewAttachedToWindow(v);
                   
}
               
}
               
@Override
               
public void onViewDetachedFromWindow(View v) {
                   
if (detach != null) {
                        detach
.onViewDetachedFromWindow(v);
                   
}
               
}
           
};
       
}

       
OnAttachStateChangeListener oldListener = ListenerUtil.trackListener(view, newListener,
                R
.id.onAttachStateChangeListener);
       
if (oldListener != null) {
            view
.removeOnAttachStateChangeListener(oldListener);
       
}
       
if (newListener != null) {
            view
.addOnAttachStateChangeListener(newListener);
       
}
   
}
}

上面的示例稍微有些复杂,因为 View 类使用 addOnAttachStateChangeListener()removeOnAttachStateChangeListener() 而不是 setter 方法, OnAttachStateChangeListenerandroid.databinding.adapters.ListenerUtil 类可帮助您跟踪这些事件, 监听器,以便在绑定适配器中将其移除。

对象转换

自动转换对象

当从绑定返回 Object 时 表达式,库会选择用于设置 属性。Object 会转换为所选方法的参数类型。这个 在应用中使用 ObservableMap 类 存储数据,如以下示例所示:

<TextView
   
android:text='@{userMap["lastName"]}'
   
android:layout_width="wrap_content"
   
android:layout_height="wrap_content" />

表达式中的 userMap 对象会返回一个值,该值会自动 转换为 setText(CharSequence) 方法中的参数类型,用于 设置 android:text 属性的值。如果参数类型为 不明确,请在表达式中转换返回值类型。

自定义转换

在某些情况下,需要在特定类型之间进行自定义转换。对于 例如,视图的 android:background 属性需要 Drawable,但 指定的 color 值是一个整数。以下示例展示了 属性需要 Drawable,但结果提供了一个整数:

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

每当需要 Drawable 且返回整数时,请转换 int ColorDrawable。 要执行转换,请使用具有 BindingConversion 注解,如下所示:

KotlinJava
@BindingConversion
fun convertColorToDrawable(color: Int) = ColorDrawable(color)
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
   
return new ColorDrawable(color);
}

但是,绑定表达式中提供的值类型必须保持一致。 您不能在同一个表达式中使用不同的类型,如下所示 示例:

// The @drawable and @color represent different value types in the same
// expression, which causes a build error.
<View
   
android:background="@{isError ? @drawable/error : @color/white}"
   
android:layout_width="wrap_content"
   
android:layout_height="wrap_content"/>

其他资源

如需详细了解数据绑定,请参阅以下资源。

示例

Codelab

博文

Discover the latest app development tools, platform updates, training, and documentation for developers across every Android device.

更新于 Aug 23, 2024

Discover the latest app development tools, platform updates, training, and documentation for developers across every Android device.

更新于 Sep 20, 2024

Discover the latest app development tools, platform updates, training, and documentation for developers across every Android device.

更新于 Dec 22, 2024