[TOC]
Jetpack学习3--使用可观察的对象&生成绑定类
使用可观察的对象
可观察性是指对象通知其他人数据变化的能力。数据绑定库允许您使对象,字段或集合可观察。
任何普通的旧对象都可以用于数据绑定,但是修改对象不会自动导致UI更新。数据绑定可用于使数据对象能够在数据发生更改时通知其他对象,即侦听器。有三种不同类型的可观察类:objects, fields, and collections.
当其中一个可观察数据对象绑定到UI并且数据对象的属性发生更改时,UI将自动更新。
可观察的字段
创建实现Observable
接口的类涉及到一些工作,如果类只有几个属性,那么这些工作就不值得了。在这种情况下,您可以使用泛型Observable
类和以下原始特定类来使字段可观察:
ObservableBoolean
ObservableByte
ObservableChar
ObservableShort
ObservableInt
ObservableLong
ObservableFloat
ObservableDouble
ObservableParcelable
可观察字段是具有单个字段的自包含可观察对象。原始版本在访问操作期间避免装箱和解箱。要使用这种机制,请在Java编程语言中创建一个public final
属性,或者在Kotlin中创建一个只读属性,如下面的示例所示:
private static class User {
public final ObservableField<String> firstName = new ObservableField<>();
public final ObservableField<String> lastName = new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
要访问字段值,使用set()和get()访问器方法,如下所示:
user.firstName.set("Google");
int age = user.age.get();
注意:Android Studio 3.1及更高版本允许您使用LiveData对象替换可观察字段,这为您的应用提供了额外的好处。有关更多信息,请参阅使用LiveData通知UI有关数据更改的信息。
可观察的集合
一些应用程序使用动态结构来保存数据。可观察集合允许使用密钥访问这些结构。如果键是引用类型,比如字符串,ObservableArrayMap
类非常有用,如下面的例子所示:
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
在布局中,可以使用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"/>
当key是int时可以使用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"/>
可观察对象
实现Observable
接口的类可以注册监听器,当可观察对象属性改变时可以通知它。
Observable
接口具有添加和删除侦听器的机制,但是您必须决定何时发送通知。为了简化开发,数据绑定库提供了BaseObservable
类,该类实现侦听器注册机制。实现BaseObservable
的数据类负责在属性发生变化时发出通知。这是通过为getter分配一个Bindable
注解,并在setter中调用notifyPropertyChanged()
方法来实现的,如下面的示例所示:
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);
}
}
数据绑定在模块包中生成一个名为BR
的类,该类包含用于数据绑定的资源的id。Bindable
注解在编译期间在BR
类文件中生成一个条目。如果不能更改数据类的基类,则可以使用PropertyChangeRegistry
对象实现Observable
接口,以有效地注册和通知侦听器。
生成绑定类
数据绑定库生成用于访问布局的变量和视图的绑定类。此页面显示如何创建和自定义生成的绑定类。
生成的绑定类将布局变量与布局中的视图链接起来。绑定类的名称和包可以customized。所有生成的绑定类都继承自ViewDataBinding
类。
为每个布局文件生成一个绑定类。默认情况下,类的名称基于布局文件的名称,将其转换为Pascal大小写并向其添加Binding后缀。上面的布局文件名是activity_main.xml
,因此相应生成的类是ActivityMainBinding
。该类保存布局属性(例如,user
变量)到布局视图的所有绑定,并且知道如何为绑定表达式赋值。
创建绑定对象
在对布局进行inflating之后,应该很快创建绑定对象,以确保在使用布局中的表达式绑定到视图之前不会修改视图层次结构。将对象绑定到布局的最常用方法是使用绑定类上的静态方法。您可以通过使用inflate()
绑定类的方法来扩展视图层次结构并将对象绑定到该层次结构,如以下示例所示:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyLayoutBinding binding = MyLayoutBinding.inflate(getLayoutInflater());
}
除了LayoutInflater对象之外,inflate()
方法还有另一个版本,它接受ViewGroup对象,如下面的示例所示:
MyLayoutBinding binding = MyLayoutBinding.inflate(getLayoutInflater(), viewGroup, false);
如果使用不同的机制对布局进行inflate,则可以将其单独绑定,如下所示:
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
有时无法预先知道绑定类型。在这种情况下,可以使用DataBindingUtil
类创建绑定,如下面的代码片段所示:
View viewRoot = LayoutInflater.from(this).inflate(layoutId, parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bind(viewRoot);
如果您在 Fragment
, ListView
, or RecyclerView
adapter,中使用数据绑定,您可能更喜欢使用bindings类或DataBindingUtil
类的inflate()
方法,如下面的代码示例所示:
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
// or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
带ID的视图
数据绑定库在绑定类中为布局中具有ID的每个视图创建一个不可变字段。例如,数据绑定库从以下布局中创建TextView类型的firstName和lastName字段:
<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>
该库一次性从视图层次结构中提取包括id在内的视图。这种机制比为布局中的每个视图调用findViewById()方法要快。
ID不是数据绑定的必要条件,但是扔有些情况需要从代码中访问视图。
变量
数据绑定库为布局中声明的每个变量生成访问器方法。例如,下面的布局在绑定类中为user,
image, and
note变量生成setter和getter方法:
<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>
ViewStubs
与普通视图不同,ViewStub
对象一开始是一个不可见的视图。当它们变得可见或被明确告知要inflate时,它们会通过inflate另一个布局来替换布局中的自己。
因为ViewStub基本上从视图层次结构中消失,所以绑定对象中的视图也必须消失,以便垃圾收集能够会是它。因为视图是最终的,所以一个ViewStubProxy对象在生成的绑定类中代替了ViewStub,当ViewStub存在时,您可以访问它,当ViewStub inflated时,您还可以访问inflated视图层次结构。
在inflating另一个布局时,必须为新布局建立绑定。因此,ViewStubProxy
必须监听ViewStub``OnInflateListener
并在需要时建立绑定。由于在给定的时间内只能存在一个侦听器,所以ViewStubProxy允许您设置一个OnInflateListener
,它在建立绑定之后调用这个OnInflateListener
。
立即绑定
当一个变量或可观察对象发生变化时,数据绑定库计划在下一帧之前执行绑定。然而,有时必须立即执行绑定。要强制执行,请使用executePendingBindings()
方法。
高级绑定
动态变量
有时,特定的绑定类是未知的。例如,RecyclerView.Adapter针对任意布局的操作不知道特定的绑定类。它仍然必须在调用onBindViewHolder()方法期间分配绑定值。
在以下示例中,RecyclerView
绑定的所有布局都具有 item
变量。该BindingHolder
对象有一个getBinding()
返回ViewDataBinding
基类的方法 。
public void onBindViewHolder(BindingHolder holder, int position) {
final T item = items.get(position);
holder.getBinding().setVariable(BR.item, item);
holder.getBinding().executePendingBindings();
}
注意:数据绑定库在模块包中生成一个名为BR的类,在上面的示例中,库自动生成BR.item
变量。
后台线程
您可以在后台线程中更改除了集合以外的数据模型。数据绑定会在计算期间隔离每一个变量/字段以避免任何并发问题
自定义绑定类名称
默认情况下,将根据布局文件的名称生成绑定类,以大写字母开头,删除下划线(_),并大写后面一个字母,且添加单词Binding作为后缀。该类放在 databinding
模块包下的包中。例如,布局文件 contact_item.xml
生成ContactItemBinding
类。如果模块包是com.example.my.app
,则绑定类放在 com.example.my.app.databinding
包中。
通过调整data
元素中的class
属性,可以重命名绑定类或将绑定类放在不同的包中 。例如,下面的布局在当前模块的databinding包中生成ContactItem绑定类:
<data class="ContactItem">
…
</data>
您可以通过在类名前加一个句点在不同的包生成绑定类。以下示例在模块包中生成绑定类:
<data class=".ContactItem">
…
</data>
你可以使用完整包名在你想要的包中生成绑定类。以下示例ContactItem
在com.example
包中创建绑定类 :
<data class="com.example.ContactItem">
…
</data>