绑定适配器负责进行适当的框架调用以设置值。一个例子是设置属性值,例如调用 setText()
方法。另一个例子是设置事件监听器,例如调用 setOnClickListener()
方法。
数据绑定库允许您指定用于设置值的方法,提供您自己的绑定逻辑,并使用适配器指定返回对象的类型。
设置属性值
每当绑定值发生更改时,生成的绑定类都必须使用绑定表达式在视图上调用 setter 方法。您可以让数据绑定库自动确定方法,也可以显式声明方法或提供自定义逻辑来选择方法。
自动方法选择
对于名为 example
的属性,库会自动查找接受兼容类型作为参数的 setExample(arg)
方法。不会考虑属性的命名空间。搜索方法时,只使用属性名称和类型。
例如,给定 android:text="@{user.name}"
表达式,库会查找接受 user.getName()
返回的类型的 setText(arg)
方法。如果 user.getName()
的返回类型为 String
,则库会查找接受 String
参数的 setText()
方法。如果表达式返回一个 int
,则库会搜索接受 int
参数的 setText()
方法。表达式必须返回正确的类型。如有必要,您可以转换返回值。
即使没有使用给定名称的属性,数据绑定也能工作。您可以使用数据绑定为任何 setter 创建属性。例如,支持类 DrawerLayout
没有属性,但它有很多 setter。以下布局会自动将 setScrimColor(int)
和 addDrawerListener(DrawerListener)
方法分别用作 app:scrimColor
和 app: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
注释将属性与 setter 关联。该注释与类一起使用,并且可以包含多个 BindingMethod
注释,每个重命名的方法一个。绑定方法是可以添加到应用中任何类的注释。
在下面的示例中,android:tint
属性与 setImageTintList(ColorStateList)
方法关联,而不是与 setTint()
方法关联。
Kotlin
@BindingMethods(value = [ BindingMethod( type = android.widget.ImageView::class, attribute = "android:tint", method = "setImageTintList")])
Java
@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
属性的绑定适配器。
Kotlin
@BindingAdapter("android:paddingLeft") fun setPaddingLeft(view: View, padding: Int) { view.setPadding(padding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()) }
Java
@BindingAdapter("android:paddingLeft") public static void setPaddingLeft(View view, int padding) { view.setPadding(padding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()); }
参数类型很重要。第一个参数确定与属性关联的视图的类型。第二个参数确定给定属性的绑定表达式中接受的类型。
绑定适配器也适用于其他类型的自定义。例如,可以从工作线程调用自定义加载程序来加载图像。
您还可以拥有接收多个属性的适配器,如下例所示。
Kotlin
@BindingAdapter("imageUrl", "error") fun loadImage(view: ImageView, url: String, error: Drawable) { Picasso.get().load(url).error(error).into(view) }
Java
@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}" />
如果imageUrl
和 error
用于 ImageView
对象,imageUrl
是字符串,而 error
是 Drawable
,则会调用适配器。如果您希望在设置任何属性时调用适配器,请将适配器的可选 requireAll
标志设置为 false
,如下例所示。
Kotlin
@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); } }
Java
@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); } }
绑定适配器方法可以在其处理程序中获取旧值。采用旧值和新值的方法必须首先声明所有属性的旧值,然后声明新值,如下例所示。
Kotlin
@BindingAdapter("android:paddingLeft") fun setPaddingLeft(view: View, oldPadding: Int, newPadding: Int) { if (oldPadding != newPadding) { view.setPadding(newPadding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()) } }
Java
@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()); } }
事件处理程序只能与具有一个抽象方法的接口或抽象类一起使用,如下例所示。
Kotlin
@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) } } }
Java
@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)
。库提供两个接口来区分它们的属性和处理程序。
Kotlin
// 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) }
Java
@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
,以指定不必为每个属性分配绑定表达式,如下例所示。
Kotlin
@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) } } }
Java
@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()
方法而不是 OnAttachStateChangeListener
的设置器方法,因此上述示例稍微复杂一些。android.databinding.adapters.ListenerUtil
类有助于跟踪这些监听器,以便可以在绑定适配器中将其移除。
对象转换
自动对象转换
当绑定表达式返回 Object
时,库会选择用于设置属性值的方法。Object
会转换为所选方法的参数类型。此行为在使用 ObservableMap
类存储数据的应用中非常方便,如下例所示。
<TextView
android:text='@{userMap["lastName"]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
表达式中的 userMap
对象返回值,该值会自动转换为在用于设置 android:text
属性值的 setText(CharSequence)
方法中找到的参数类型。如果参数类型不明确,请在表达式中转换返回类型。
自定义转换
在某些情况下,需要在特定类型之间进行自定义转换。例如,视图的 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
注释的静态方法,如下所示。
Kotlin
@BindingConversion fun convertColorToDrawable(color: Int) = ColorDrawable(color)
Java
@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"/>
其他资源
要了解有关数据绑定的更多信息,请参阅以下资源。