这是 Data Binding 学习笔记的最后一篇,我觉得 Attribute Setters 的特性大大提高了 Data Binding 库的可用性、拓展性。以下主要翻译自官方文档。

Attribute Setters(属性 Setter)

当一个 View 绑定的数据发生变动时,自动生成的 binding 类其实会根据 xml 中的 binding 表达式来调用 View 对应属性的 setter 函数。Data binding 框架内置了几种自定义赋值的方法。

Automatic Setters

如果 UI 控件内的一个 attribute 叫 xxx,data binding 会尝试寻找对应的 setXXX 函数。

比如,针对一个与 TextView 的 android:text 绑定的表达式,data binding 会自动寻找 setText(CharSequence) 函数;如果表达式返回值为 int 类型,则会寻找 setText(int) 函数。

Renamed Setters

如果 attribute 的命名与 setter函数不对应,我们可以用 BindingMethods 注解来将 attribute 与 setter 绑定到一起。举个例子,android:tint 属性可以这样与 setImageTintList(ColorStateList) 绑定,而不是 setTint:

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

Android 框架中的 setter 重命名已经在库中实现了,开发者只需要专注于自己的 setter。

Custom Setters

一些属性需要自定义 setter 逻辑。例如,目前没有与 android:paddingLeft 对应的 setter,只有一个 setPadding(left, top, right, bottom) 函数。结合静态 binding adapter 函数与 BindingAdapter 注解可以让开发者自定义属性 setter。

Android 属性已经内置一些 BindingAdapter。例如,这是一个 paddingLeft 的自定义 setter:

1
2
3
4
5
6
7
@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}

Binding adapter 在其他自定义类型上也很好用。举个例子,一个 loader 可以在非主线程加载图片。

当存在冲突时,开发者创建的 binding adapter 会覆盖 data binding 的默认 adapter。

你也可以创建多个参数的 adapter:

1
2
3
4
BindingAdapter({"bind:imageUrl", "bind:error"})
public static void loadImage(ImageView view, String url, Drawable error) {
Picasso.with(view.getContext()).load(url).error(error).into(view);
}
1
2
<ImageView app:imageUrl=“@{venue.imageUrl}”
app:error=“@{@drawable/venueError}”/>

imageUrlerror 存在时这个 adapter 会被调用。imageUrl 是一个 String,error 是一个 Drawable。

  • 在匹配时自定义命名空间会被忽略
  • 你可以为 android 命名空间编写 adapter

Binding adapter 方法可以获取旧的赋值。只需要将旧值放置在前,新值放置在后:

1
2
3
4
5
6
7
8
9
@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());
}
}

事件 handler 仅可用于只拥有一个抽象方法的接口或者抽象类。例如:

1
2
3
4
5
6
7
8
9
10
11
12
@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);
}
}
}

当 listener 内置多个函数时,必须分割成多个 listener。例如,View.OnAttachStateChangeListener 内置两个函数:onViewAttachedToWindow()onViewDetachedFromWindow()。在这里必须为两个不同的属性创建不同的接口。

1
2
3
4
5
6
7
8
9
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewDetachedFromWindow {
void onViewDetachedFromWindow(View v);
}

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

因为改变一个 listener 会影响到另外一个,我们必须编写三个不同的 adapter,包括修改一个属性的,和修改两个属性的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@BindingAdapter("android:onViewAttachedToWindow")
public static void setListener(View view, OnViewAttachedToWindow attached) {
setListener(view, null, attached);
}

@BindingAdapter("android:onViewDetachedFromWindow")
public static void setListener(View view, OnViewDetachedFromWindow detached) {
setListener(view, detached, null);
}

@BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"})
public static void setListener(View view, final OnViewDetachedFromWindow detach,
final OnViewAttachedToWindow attach) {
if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) {
final 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);
}
}
};
}
final OnAttachStateChangeListener oldListener = ListenerUtil.trackListener(view,
newListener, R.id.onAttachStateChangeListener);
if (oldListener != null) {
view.removeOnAttachStateChangeListener(oldListener);
}
if (newListener != null) {
view.addOnAttachStateChangeListener(newListener);
}
}
}

上面的例子比普通情况下复杂,因为 View 是 add/remove View.OnAttachStateChangeListener 而不是 set。android.databinding.adapters.ListenerUtil 可以用来辅助跟踪旧的 listener 并移除它。

对应 addOnAttachStateChangeListener(View.OnAttachStateChangeListener) 支持的 api 版本,通过向 OnViewDetachedFromWindowOnViewAttachedToWindow 添加 @TargetApi(VERSION_CODES.HONEYCHOMB_MR1) 注解,data binding 代码生成器会知道这些 listener 只会在 Honeycomb MR1 或更新的设备上使用。

(完)