[TOC]
Jetpack學習3--使用可觀察的對象&生成綁定類
使用可觀察的對象
可觀察性是指對象通知其他人數(shù)據變化的能力。數(shù)據綁定庫允許您使對象,字段或集合可觀察。
任何普通的舊對象都可以用于數(shù)據綁定,但是修改對象不會自動導致UI更新。數(shù)據綁定可用于使數(shù)據對象能夠在數(shù)據發(fā)生更改時通知其他對象,即偵聽器。有三種不同類型的可觀察類:objects, fields, and collections.
當其中一個可觀察數(shù)據對象綁定到UI并且數(shù)據對象的屬性發(fā)生更改時,UI將自動更新。
可觀察的字段
創(chuàng)建實現(xiàn)Observable
接口的類涉及到一些工作,如果類只有幾個屬性,那么這些工作就不值得了。在這種情況下,您可以使用泛型Observable
類和以下原始特定類來使字段可觀察:
ObservableBoolean
ObservableByte
ObservableChar
ObservableShort
ObservableInt
ObservableLong
ObservableFloat
ObservableDouble
ObservableParcelable
可觀察字段是具有單個字段的自包含可觀察對象。原始版本在訪問操作期間避免裝箱和解箱。要使用這種機制,請在Java編程語言中創(chuàng)建一個public final
屬性,或者在Kotlin中創(chuàng)建一個只讀屬性,如下面的示例所示:
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有關數(shù)據更改的信息。
可觀察的集合
一些應用程序使用動態(tài)結構來保存數(shù)據。可觀察集合允許使用密鑰訪問這些結構。如果鍵是引用類型,比如字符串,ObservableArrayMap
類非常有用,如下面的例子所示:
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
在布局中,可以使用key使用map中的數(shù)據,如下:
<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"/>
可觀察對象
實現(xiàn)Observable
接口的類可以注冊監(jiān)聽器,當可觀察對象屬性改變時可以通知它。
Observable
接口具有添加和刪除偵聽器的機制,但是您必須決定何時發(fā)送通知。為了簡化開發(fā),數(shù)據綁定庫提供了BaseObservable
類,該類實現(xiàn)偵聽器注冊機制。實現(xiàn)BaseObservable
的數(shù)據類負責在屬性發(fā)生變化時發(fā)出通知。這是通過為getter分配一個Bindable
注解,并在setter中調用notifyPropertyChanged()
方法來實現(xiàn)的,如下面的示例所示:
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);
}
}
數(shù)據綁定在模塊包中生成一個名為BR
的類,該類包含用于數(shù)據綁定的資源的id。Bindable
注解在編譯期間在BR
類文件中生成一個條目。如果不能更改數(shù)據類的基類,則可以使用PropertyChangeRegistry
對象實現(xiàn)Observable
接口,以有效地注冊和通知偵聽器。
生成綁定類
數(shù)據綁定庫生成用于訪問布局的變量和視圖的綁定類。此頁面顯示如何創(chuàng)建和自定義生成的綁定類。
生成的綁定類將布局變量與布局中的視圖鏈接起來。綁定類的名稱和包可以customized。所有生成的綁定類都繼承自ViewDataBinding
類。
為每個布局文件生成一個綁定類。默認情況下,類的名稱基于布局文件的名稱,將其轉換為Pascal大小寫并向其添加Binding后綴。上面的布局文件名是activity_main.xml
,因此相應生成的類是ActivityMainBinding
。該類保存布局屬性(例如,user
變量)到布局視圖的所有綁定,并且知道如何為綁定表達式賦值。
創(chuàng)建綁定對象
在對布局進行inflating之后,應該很快創(chuàng)建綁定對象,以確保在使用布局中的表達式綁定到視圖之前不會修改視圖層次結構。將對象綁定到布局的最常用方法是使用綁定類上的靜態(tài)方法。您可以通過使用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
類創(chuàng)建綁定,如下面的代碼片段所示:
View viewRoot = LayoutInflater.from(this).inflate(layoutId, parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bind(viewRoot);
如果您在 Fragment
, ListView
, or RecyclerView
adapter,中使用數(shù)據綁定,您可能更喜歡使用bindings類或DataBindingUtil
類的inflate()
方法,如下面的代碼示例所示:
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
// or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
帶ID的視圖
數(shù)據綁定庫在綁定類中為布局中具有ID的每個視圖創(chuàng)建一個不可變字段。例如,數(shù)據綁定庫從以下布局中創(chuàng)建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不是數(shù)據綁定的必要條件,但是扔有些情況需要從代碼中訪問視圖。
變量
數(shù)據綁定庫為布局中聲明的每個變量生成訪問器方法。例如,下面的布局在綁定類中為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
必須監(jiān)聽ViewStub``OnInflateListener
并在需要時建立綁定。由于在給定的時間內只能存在一個偵聽器,所以ViewStubProxy允許您設置一個OnInflateListener
,它在建立綁定之后調用這個OnInflateListener
。
立即綁定
當一個變量或可觀察對象發(fā)生變化時,數(shù)據綁定庫計劃在下一幀之前執(zhí)行綁定。然而,有時必須立即執(zhí)行綁定。要強制執(zhí)行,請使用executePendingBindings()
方法。
高級綁定
動態(tài)變量
有時,特定的綁定類是未知的。例如,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();
}
注意:數(shù)據綁定庫在模塊包中生成一個名為BR的類,在上面的示例中,庫自動生成BR.item
變量。
后臺線程
您可以在后臺線程中更改除了集合以外的數(shù)據模型。數(shù)據綁定會在計算期間隔離每一個變量/字段以避免任何并發(fā)問題
自定義綁定類名稱
默認情況下,將根據布局文件的名稱生成綁定類,以大寫字母開頭,刪除下劃線(_),并大寫后面一個字母,且添加單詞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
包中創(chuàng)建綁定類 :
<data class="com.example.ContactItem">
…
</data>