[TOC]
Jetpack学习4—绑定适配器
绑定适配器负责进行适当的框架调用来设置值。一个例子是设置一个属性值,比如调用setText()方法。另一个例子是设置一个事件监听器,比如调用setOnClickListener()方法。
数据绑定库允许您调用指定的方法来设置值,提供您自己的绑定逻辑,并通过使用适配器指定返回对象的类型。
设置属性值
每当绑定值发生更改时,生成的绑定类必须使用绑定表达式调用视图上的setter方法。你可以允许数据绑定库自动确定方法、显式声明方法或提供自定义逻辑来选择方法。
自动方法选择
对于名为example的属性,库会自动尝试找到接受兼容类型作为参数的方法setExample(arg)
。不考虑属性的命名空间,只在搜索方法时使用属性名称和类型。
例如,给定android:text="@{user.name}"
表达式,库将查找一个setText(arg)
方法,该方法接受user.getName()
返回的类型。如果user.getName()
的返回类型是字符串,则库将查找接受字符串参数的setText()
方法。如果表达式返回一个int,那么库将搜索一个接受int参数的setText()
方法。表达式必须返回正确的类型,如果需要,可以强制转换返回值。
即使没有给定名称的属性存在,数据绑定也可以工作。然后可以使用数据绑定为任何setter创建属性。例如,支持类DrawerLayout
没有任何属性,但有很多setter。以下布局分别自动使用 setScrimColor(int)
和setDrawerListener(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
注释将属性与设置器相关联 。注释与类一起使用,可以包含多个 BindingMethod
注释,每个注释方法一个注释。绑定方法是可以添加到应用程序中任何类的注释。在以下示例中,android:tint
属性与setImageTintList(ColorStateList)
方法关联,而不是与 setTint()
方法关联:
@BindingMethods({
@BindingMethod(type = "android.widget.ImageView",
attribute = "android:tint",
method = "setImageTintList"),
})
大多数情况下,您不需要在Android framework类中重命名setter。属性已经使用名称约定实现,用于自动查找匹配的方法。
提供自定义逻辑
某些属性需要自定义绑定逻辑。例如,该android:paddingLeft
属性没有关联的setter 。相反,setPadding(left, top, right, bottom)
提供了该方法。带BindingAdapter
注释的静态绑定适配器方法允许您自定义如何调用属性的setter。
Android framework类的属性已经创建了BindingAdapter注释。例如,下面的示例显示了paddingLeft属性的绑定适配器:
@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
参数类型很重要。第一个参数确定与该属性关联的视图的类型。第二个参数确定给定属性的绑定表达式中接受的类型。
绑定适配器对于其他类型的定制很有用。例如,可以从工作线程调用自定义加载程序来加载图像。
当发生冲突时,您定义的绑定适配器将覆盖Android框架提供的默认适配器。
您还可以使用接收多个属性的适配器,如下面的示例所示:
@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类型。该适配器会被调用。如果你想当set任何一个属性就调用该适配器,你可以将适配器的可选requireAll
标志设置 为false
,如以下示例所示:
@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);
}
}
注意:发生冲突时,绑定适配器会覆盖默认数据绑定适配器。
绑定适配器方法可以选择在其处理程序中接受旧值。采用新旧值的方法应该首先声明属性的所有旧值,然后声明新值,如下例所示:
@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());
}
}
事件处理程序只能与带有一个抽象方法的接口或抽象类一起使用,如以下示例所示:
@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)
。该库提供了两个接口来区分它们的属性和处理程序:
@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
指定不是必须为每个属性分配绑定表达式,如以下示例所示:
@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类设置OnAttachStateChangeListener使用的是addOnAttachStateChangeListener()和removeOnAttachStateChangeListener()方法而不是setter方法 。android.databinding.adapters.ListenerUtil
类可以帮助跟踪前面的侦听器,以便在绑定适配器中删除它们。
通过使用@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
注释OnViewDetachedFromWindow
和OnViewAttachedToWindow
接口,数据绑定代码生成器知道侦听器只应该在Android 3.1 (API级别12)及更高版本上运行时生成,该版本支持addOnAttachStateChangeListener()方法。
对象转换
自动对象转换
当从绑定表达式返回Object时,库将选择用于设置属性值的方法。Object
被转换为所选方法的参数类型。这种行为在使用ObservableMap
类存储数据的应用程序中非常方便,如下图所示:
<TextView
android:text='@{userMap["lastName"]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
注意:您还可以使用object.key
引用map
中的值。例如,上面例子中的@{userMap["lastName"]}
可以替换为@{userMap.lastName}
。
表达式中的userMap
对象返回一个值,该值自动转换为setText(CharSequence)
方法中的参数类型,该方法用于设置android:text
属性的值。如果参数类型不明确,则必须在表达式中强制转换返回类型。
自定义转化
在某些情况下,需要在特定类型之间进行自定义转换。例如,视图的android:background
属性需要一个Drawable
,但是指定的颜色值是一个整数。下面的示例显示了一个属性,该属性需要一个Drawable
,但是提供了一个整数:
<View
android:background="@{isError ? @color/red : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
无论何时需要绘制并且返回整数,都应该将int转换为ColorDrawable。可以使用带有BindingConversion
注释的静态方法进行转换,如下所示:
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}
但是,绑定表达式中提供的值类型必须一致。你不能在同一个表达式中使用不同的类型,如下面的例子所示:
<View
android:background="@{isError ? @drawable/error : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
网友评论