转载

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

数据对象

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

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

Observable 对象

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

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

private static class User extends BaseObservable {    private String firstName;    private String lastName;    @Bindable    public String getFirstName() {     return this.firstName;    }    @Bindable    public String getFirstName() {     return this.lastName;    }    public void setFirstName(String firstName) {     this.firstName = firstName;     notifyPropertyChanged(BR.firstName);    }    public void setLastName(String lastName) {     this.lastName = lastName;     notifyPropertyChanged(BR.lastName);    } } 

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

ObservableFields

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

private static class User extends BaseObservable {    public final ObservableField<String> firstName =        new ObservableField<>();    public final ObservableField<String> lastName =        new ObservableField<>();    public final ObservableInt age = new ObservableInt(); }

很简单,这些变量会自动触发值改变事件,使用 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.inflate(layoutInflater); MyLayoutBinding binding = MyLayoutBinding.inflate(LayoutInflater, viewGroup, false);

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

MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);

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

ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,     parent, attachToParent); ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);

(带 ID 的 View)Views With IDs

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

例如:

<layout xmlns:android="http://schemas.android.com/apk/res/android">    <data>     <variable name="user" type="com.example.User"/>    </data>    <LinearLayout     android:orientation="vertical"     android:layout_width="match_parent"     android:layout_height="match_parent">     <TextView android:layout_width="wrap_content"      android:layout_height="wrap_content"      android:text="@{user.firstName}"    android:id="@+id/firstName"/>     <TextView android:layout_width="wrap_content"      android:layout_height="wrap_content"      android:text="@{user.lastName}"   android:id="@+id/lastName"/>    </LinearLayout> </layout> 

生成的 绑定类会包含如下变量:

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

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

正文到此结束
Loading...