4. 数据对象
任何 POJO 都能用在 data binding 中,但是更改 POJO 并不会同步更新 UI。DataBinding 的真正强大之处在于它可以让你的数据对象拥有更新通知的能力。DataBinding 提供了三种的数据改变通知机制,Observable 对象,observable 字段,与 observable 容器类。
当上面的 observable 对象绑定在 UI 上,对象的属性数据发生变化时,UI 就会同步更新。
4.1. Observable 对象
当一个类实现了 Observable 接口时,data binding 会设置一个 listener 绑定到的对象上,以便监听对象字段的变动。
Observable 接口有添加/移除 listener 的机制,但发出通知还需要开发者来做。为了方便开发者,我们创建了一个基类 BaseObservable,它已经实现 listener 注册机制,开发者只需要实现字段值变化的通知就行。
开发者只需要像下面例子一样,1.在 getter 上使用 Bindable 注解,2.在 setter 中发出通知:
private static class User extends BaseObservable {
private String firstName;
private String lastName;
@Bindable
public String getFirstName() {
return this.firstName;
}
@Bindable
public String getLastName() {
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 类会生成在 module 的 package 下。如果数据对象的基类不可修改,那么使用 Observable 接口 + PropertyChangeRegistry 可以方便实现注册 listener 并且通知数据改变。比如 BaseObservable 的实现:
public class BaseObservable implements Observable {
private transient PropertyChangeRegistry mCallbacks;
public BaseObservable() {
}
@Override
public synchronized void addOnPropertyChangedCallback(OnPropertyChangedCallback callback) {
if (mCallbacks == null) {
mCallbacks = new PropertyChangeRegistry();
}
mCallbacks.add(callback);
}
@Override
public synchronized void removeOnPropertyChangedCallback(OnPropertyChangedCallback callback) {
if (mCallbacks != null) {
mCallbacks.remove(callback);
}
}
/**
* Notifies listeners that all properties of this instance have changed.
*/
public synchronized void notifyChange() {
if (mCallbacks != null) {
mCallbacks.notifyCallbacks(this, 0, null);
}
}
/**
* Notifies listeners that a specific property has changed. The getter for the property
* that changes should be marked with {@link Bindable} to generate a field in
* <code>BR</code> to be used as <code>fieldId</code>.
*
* @param fieldId The generated BR id for the Bindable field.
*/
public void notifyPropertyChanged(int fieldId) {
if (mCallbacks != null) {
mCallbacks.notifyCallbacks(this, fieldId, null);
}
}
}
4.2. Observable 属性
创建 Observable 类还是需要花费一点时间的,如果开发者想要省时,或者数据类的字段很少的话,可以使用
- ObservableField
- ObservableBoolean
- ObservableByte
- ObservableChar
- ObservableShort
- ObservableInt
- ObservableLong
- ObservableFloat
- ObservableDouble
- ObservableParcelable
ObservableFields 是包含一个字段的自包含 observable 对象。原始版本避免了在存取过程中做打包/解包操作。使用它的话,应该在数据类中创建一个 public final 字段:
private static class User {
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();
4.3. Observable 容器类
一些应用会使用更加灵活的结构来保存数据。Observable 容器类允许使用 key 来获取这类数据。
4.3.1 当 key 是类似 String 的引用类型时,使用 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<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"/>
4.3.2 当 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<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"/>
4.4. 双向绑定
双向绑定是指当 View 属性值修改的时候,也能够修改绑定的 model 的数据。双向绑定用法很简单,在要使用双向绑定的地方,使用 “@={}” 语法即可。
<EditText android:text="@={user.firstName}" />
注意,这里的 firstName 必须是 ObservableField <T> 类型。
双向绑定只适用于那些某个属性绑定监听事件的控件,如
- TextView/EditView/Button (android:text, TextWatcher)
- CheckBox (android:checked, OnCheckedChangeListener)
- DatePicker(android:year, android:month, android:day, OnDateChangedListener)
- TimePicker(android:hour, android:minute, OnTimeChangedListener)
- RatingBar(android:rating, OnRatingBarChangeListener)
- …
大部分控件都能满足双向绑定的需求,实在不行就自定义满足该要求的控件吧。