来自官方的Android数据绑定(Data Binding)框架②

作者: rain 分类: 移动 发布时间: 2015-06-04 10:18 6 条评论

数据对象
任何的POJO 对象都可以用作数据绑定,但是修改一个 POJO 对象不会更新 UI。 数据绑定的威力在于,赋予数据对象在数据改变的时候通知其他组件的能力。有三种数据改变通知机制:Observable 对象、ObservableFields 和 observable 集合。

如果这三种类型中的任意一种类型的数据绑定到 UI 中,当数据改变的时候, UI 的数据也会自动更新。

Observable 对象

实现了 android.databinding.Observable 接口的对象,可以设置一个监听器来监听所有值域变化的事件。

为了方便开发者使用,BaseObservable 类包含了添加和删除监听对象的接口,但是通知数据变化需要开发者自己来做。 和 ListView 的 Adapter 类似。

Bindable 注解在编译的时候会生成一个 BR 类中的实体,BR 位于模块的包中。如果您的数据类无法修改,则可以使用 PropertyChangeRegistry 来保存和通知改变事件。

ObservableFields

继承 Observable 可能有点麻烦,如果你像简单一点或者只有少量几个绑定的属性,则可以使用 ObservableFields。 ObservableFields 为字包含的 observable 对象。 ObservableFields 包含了所有基本类型和一个引用类型。 使用方式如下:

很简单,这些变量会自动触发值改变事件,使用 get 和 set 来访问:

user.firstName.set(“Google”);
int age = user.age.get();

Observable 集合
如果引用的 key 为对象,则可以使用 ObservableArrayMap :

ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put(“firstName”, “Google”);
user.put(“lastName”, “Inc.”);
user.put(“age”, 17);

在布局文件中,可以通过 String key 来引用map 里面的对象:

<data>
<import type=”android.databinding.ObservableMap”/>
<variable name=”user” type=”ObservableMap&lt;String, Object>”/>
</data>

<TextView
android:text=’@{user[“lastName”]}’
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”/>
<TextView
android:text=’@{String.valueOf(1 + (Integer)user[“age”])}’
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”/>

如果集合的 key 为整数,则使用 ObservableArrayList :

ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add(“Google”);
user.add(“Inc.”);
user.add(17);

在 布局文件中可以使用索引来引用这些对象:

<data>
<import type=”android.databinding.ObservableList”/>
<import type=”com.example.my.app.Fields”/>
<variable name=”user” type=”ObservableList&lt;Object>”/>
</data>

<TextView
android:text=’@{user[Fields.LAST_NAME]}’
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”/>
<TextView
android:text=’@{String.valueOf(1 + (Integer)user[Fields.AGE])}’
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”/>

生成的绑定类(Generated Binding)

生成的绑定类自动处理的 布局文件中的 View 和 变量的值,并把他们关联起来。 所有生成的绑定类都继承自 android.databinding.ViewDataBinding。

创建绑定类

绑定类应该在解析完布局后立刻创建,这样可以避免其他数据干扰布局文件中表达式的解析。获取绑定类最常用的方式是通过生成类的静态函数 inflate 。inflate 函数同时解析 View 和完成数据绑定。

如果布局文件解析的机制有变化,则还可以分开绑定:

MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);

有时候,绑定对象需要运行时创建,则可以通过如下方式:

(带 ID 的 View)Views With IDs

对于 布局文件中的每个带 ID 的 View 都会生成一个 final 变量。 绑定类只解析一次布局文件,并创建每个 View。 这种方式比多次调用 findViewById 要高效一些。

例如:

生成的 绑定类会包含如下变量:
public final TextView firstName;
public final TextView lastName;

没有 ID 也可以使用数据绑定,但是为了以后引用这些 View, 添加个 id 会更加方便。

变量(Variables)

每个变量都会生成 get 和 set 函数:
<data>
<import type=”android.graphics.drawable.Drawable”/>
<variable name=”user” type=”com.example.User”/>
<variable name=”image” type=”Drawable”/>
<variable name=”note” type=”String”/>
</data>

会生成如下代码:
public abstract com.example.User getUser();
public abstract void setUser(com.example.User user);
public abstract Drawable getImage();
public abstract void setImage(Drawable image);
public abstract String getNote();
public abstract void setNote(String note);

ViewStubs

ViewStubs 和普通的 view 不太一样。 一开始这些 view 是不可见的, 并且没有解析到 界面中,当显示 ViewStub 或者显示的解析他们的时候才会加载到界面中,替代之前的 View。

由于 ViewStub 最终会从 View 层级中消失, 所以对应的绑定对象也应该消失以便回收资源。由于 View 是 final 的,这里会使用一个 ViewStubProxy 对象来替代 ViewStub, 这样开发者就可以访问 ViewStub 了,并且当 ViewStub 被加载到 View 层级中的时候,开发者也可以访问加载的 View。

当解析另外一个布局文件的时候, 绑定对象也应该和新的布局关联起来。因此,ViewStubProxy 需要监听 ViewStub 的 OnInflateListener 回调接口来建立绑定关系。开发者可以在 ViewStubProxy 上设置一个 OnInflateListener ,当绑定建立的时候,开发者可以收到回调 函数。

高级绑定

动态变量
有时候具体绑定的类还不知道是哪个。例如,一个 RecyclerView Adapter 使用一些布局文件,只有在 onBindViewHolder 中才知道 layout 使用的是哪个变量。
下面的例子中,RecyclerView 的每个 View 都包含一个 item 变量, 通过 BindingHolder 的 getBinding 函数来访问 ViewDataBinding 。然后把 item 变量设置进去。
public void onBindViewHolder(BindingHolder holder, int position) {
final T item = mItems.get(position);
holder.getBinding().setVariable(BR.item, item);
holder.getBinding().executePendingBindings();
}

立即绑定
当变量的值更新的时候,binding 对象将在下个更新周期中更新。这样就会有一点时间间隔,如果你像立刻更新,则可以使用 executePendingBindings 函数。

后台线程

只要不是集合变量,则可以在后台线程中更新数据。数据绑定将会保存每个变量的值到本地以避免多线程问题。

Attribute Setters

当绑定的值改变的时候,生成的绑定对象会调用一个 setter 函数来更新 View 的值。绑定框架可以自定义调用哪个函数来设置值。

自动查找 setter

对于一个属性,绑定框架会自动查找 setAttribute 函数。例如 TextView 的属性 android:text 上的表达式,绑定框架将会调用 TextView 的 setText(String) 函数,如果表达式返回值为 int, 则会调用 setText(int) 函数。所以,要小心表达式的返回值,如果必要可以使用 cast 来转换为需要的类型。

需要注意的是, 数据绑定框架查找的是一个 set 函数,而不是该属性是否存在。 例如 support 库中的 DrawerLayout 没有任何属性,但是有很多 set 函数,所以可以把这些函数当做属性来在 绑定布局文件中使用,只需要把函数名字的 set 去掉,并把后面的单词首字符修改为小写即可。例如:

<android.support.v4.widget.DrawerLayout
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
app:scrimColor=”@{@color/scrim}”
app:drawerListener=”@{fragment.drawerListener}”/>

DrawerLayout 有个 setScrimColor 函数,但是没有 scrimColor 这个变量。

重命名 setter

还可以通过 BindingMethods 来重命名对应的 set 函数。 例如 android:tint 属性的 set 函数被重命名为 setImageTintList 而不是 setTint.

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

开发者一般不需要重命名 setter, android 框架已经重命名了对应的实现。

自定义 Setters
有些属性需要自定义绑定逻辑。例如, android:paddingLeft 属性并没有对应的函数, View 只有一个 setPadding(left, top, right, bottom)。通过 BindingAdapter 注解来创建一个静态的自定义 setter 函数。 android 系统已经创建了这些 BindingAdapter 函数了,例如下面是 paddingLeft 属性对应的函数:

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

绑定适配器(Binding adapter)对于其他类型的定制是非常有用的。例如一个自定义的 loader 可以在其他线程中加载图片。

如果绑定适配器有冲突,则开发者自定义的将会替代系统自定义的。

一个 适配器还可以有多个参数:

@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);
}

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

如果用于 ImageView 的 imageUrl和 error 参数都存在并且 imageUrl 是 string 类型、error 是 drawable 类型 则就会调用上面定义的适配器。

在匹配适配器的时候, 会忽略自定义的命名空间
你也可以为 android 命名空间的属性自定义适配器

转换器(Converters)

对象转换

当绑定表达式返回一个对象时候,将会自动调用 set 函数、重命名的函数、或者自定义的 setter 中的一个。表达式返回的对象将会转换为该函数的参数类型。

使用 ObservableMaps 来保存数据会比较简单。例如:
<TextView
android:text=’@{userMap[“lastName”]}’
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”/>

这里的 userMap 返回的对象将制动转换为 setText(CharSequence) 的参数。 如果参数不明确,则开发者需要强制转换为需要的类型。

自定义转换规则

有时候参数应该可以自动转换,例如
<View
android:background=”@{isError ? @color/red : @color/white}”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”/>

上面的背景需要一个 Drawable 对象,但是表达式的返回值为整数对象的颜色值。这种情况下,颜色值需要转换为 ColorDrawable。 这种转换通过一个静态函数完成,该函数带有一个 BindingConversion 注解。

@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}
需要注意的是,转换是在 setter 层面上完成的, 所以不能混合使用不同的类型:

<View
android:background=”@{isError ? @drawable/error : @color/white}”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”/>

上面混合使用 drawable 和 int 是不可以的。

由于数据绑定框架还处于beta 测试阶段, 上面所介绍的方法可能随时出现更改,最新的信息,请参考这里:https://developer.android.com/tools/data-binding/guide.html

两个数据绑定的示例项目:

本文出自 云在千峰,转载时请注明出处及相应链接。

本文永久链接: http://blog.chengyunfeng.com/?p=735

Ɣ回顶部