Data Binding学习笔记——Attribute Setters

这是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 或更新的设备上使用。