[TOC]
Jetpack學(xué)習(xí)3--使用可觀察的對(duì)象&生成綁定類(lèi)
使用可觀察的對(duì)象
可觀察性是指對(duì)象通知其他人數(shù)據(jù)變化的能力硅急。數(shù)據(jù)綁定庫(kù)允許您使對(duì)象,字段或集合可觀察萨西。
任何普通的舊對(duì)象都可以用于數(shù)據(jù)綁定妄壶,但是修改對(duì)象不會(huì)自動(dòng)導(dǎo)致UI更新霞扬。數(shù)據(jù)綁定可用于使數(shù)據(jù)對(duì)象能夠在數(shù)據(jù)發(fā)生更改時(shí)通知其他對(duì)象佑笋,即偵聽(tīng)器翼闹。有三種不同類(lèi)型的可觀察類(lèi):objects, fields, and collections.
當(dāng)其中一個(gè)可觀察數(shù)據(jù)對(duì)象綁定到UI并且數(shù)據(jù)對(duì)象的屬性發(fā)生更改時(shí),UI將自動(dòng)更新蒋纬。
可觀察的字段
創(chuàng)建實(shí)現(xiàn)Observable
接口的類(lèi)涉及到一些工作猎荠,如果類(lèi)只有幾個(gè)屬性,那么這些工作就不值得了蜀备。在這種情況下关摇,您可以使用泛型Observable
類(lèi)和以下原始特定類(lèi)來(lái)使字段可觀察:
ObservableBoolean
ObservableByte
ObservableChar
ObservableShort
ObservableInt
ObservableLong
ObservableFloat
ObservableDouble
ObservableParcelable
可觀察字段是具有單個(gè)字段的自包含可觀察對(duì)象。原始版本在訪問(wèn)操作期間避免裝箱和解箱琼掠。要使用這種機(jī)制,請(qǐng)?jiān)贘ava編程語(yǔ)言中創(chuàng)建一個(gè)public final
屬性停撞,或者在Kotlin中創(chuàng)建一個(gè)只讀屬性瓷蛙,如下面的示例所示:
private static class User {
public final ObservableField<String> firstName = new ObservableField<>();
public final ObservableField<String> lastName = new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
要訪問(wèn)字段值,使用set()和get()訪問(wèn)器方法戈毒,如下所示:
user.firstName.set("Google");
int age = user.age.get();
注意:Android Studio 3.1及更高版本允許您使用LiveData對(duì)象替換可觀察字段艰猬,這為您的應(yīng)用提供了額外的好處。有關(guān)更多信息埋市,請(qǐng)參閱使用LiveData通知UI有關(guān)數(shù)據(jù)更改的信息冠桃。
可觀察的集合
一些應(yīng)用程序使用動(dòng)態(tài)結(jié)構(gòu)來(lái)保存數(shù)據(jù)〉勒可觀察集合允許使用密鑰訪問(wèn)這些結(jié)構(gòu)食听。如果鍵是引用類(lèi)型,比如字符串污茵,ObservableArrayMap
類(lèi)非常有用樱报,如下面的例子所示:
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
在布局中,可以使用key使用map中的數(shù)據(jù)泞当,如下:
<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"/>
當(dāng)key是int時(shí)可以使用ObservableArrayList
迹蛤,如下:
ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);
在布局中,可以通過(guò)索引訪問(wèn)列表,如以下示例所示:
<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"/>
可觀察對(duì)象
實(shí)現(xiàn)Observable
接口的類(lèi)可以注冊(cè)監(jiān)聽(tīng)器盗飒,當(dāng)可觀察對(duì)象屬性改變時(shí)可以通知它嚷量。
Observable
接口具有添加和刪除偵聽(tīng)器的機(jī)制,但是您必須決定何時(shí)發(fā)送通知逆趣。為了簡(jiǎn)化開(kāi)發(fā)蝶溶,數(shù)據(jù)綁定庫(kù)提供了BaseObservable
類(lèi),該類(lèi)實(shí)現(xiàn)偵聽(tīng)器注冊(cè)機(jī)制汗贫。實(shí)現(xiàn)BaseObservable
的數(shù)據(jù)類(lèi)負(fù)責(zé)在屬性發(fā)生變化時(shí)發(fā)出通知身坐。這是通過(guò)為getter分配一個(gè)Bindable
注解,并在setter中調(diào)用notifyPropertyChanged()
方法來(lái)實(shí)現(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ù)據(jù)綁定在模塊包中生成一個(gè)名為BR
的類(lèi)部蛇,該類(lèi)包含用于數(shù)據(jù)綁定的資源的id。Bindable
注解在編譯期間在BR
類(lèi)文件中生成一個(gè)條目咐蝇。如果不能更改數(shù)據(jù)類(lèi)的基類(lèi)涯鲁,則可以使用PropertyChangeRegistry
對(duì)象實(shí)現(xiàn)Observable
接口,以有效地注冊(cè)和通知偵聽(tīng)器有序。
生成綁定類(lèi)
數(shù)據(jù)綁定庫(kù)生成用于訪問(wèn)布局的變量和視圖的綁定類(lèi)抹腿。此頁(yè)面顯示如何創(chuàng)建和自定義生成的綁定類(lèi)。
生成的綁定類(lèi)將布局變量與布局中的視圖鏈接起來(lái)旭寿。綁定類(lèi)的名稱(chēng)和包可以customized警绩。所有生成的綁定類(lèi)都繼承自ViewDataBinding
類(lèi)。
為每個(gè)布局文件生成一個(gè)綁定類(lèi)盅称。默認(rèn)情況下肩祥,類(lèi)的名稱(chēng)基于布局文件的名稱(chēng),將其轉(zhuǎn)換為Pascal大小寫(xiě)并向其添加Binding后綴缩膝。上面的布局文件名是activity_main.xml
混狠,因此相應(yīng)生成的類(lèi)是ActivityMainBinding
。該類(lèi)保存布局屬性(例如疾层,user
變量)到布局視圖的所有綁定将饺,并且知道如何為綁定表達(dá)式賦值。
創(chuàng)建綁定對(duì)象
在對(duì)布局進(jìn)行inflating之后痛黎,應(yīng)該很快創(chuàng)建綁定對(duì)象予弧,以確保在使用布局中的表達(dá)式綁定到視圖之前不會(huì)修改視圖層次結(jié)構(gòu)。將對(duì)象綁定到布局的最常用方法是使用綁定類(lèi)上的靜態(tài)方法湖饱。您可以通過(guò)使用inflate()
綁定類(lèi)的方法來(lái)擴(kuò)展視圖層次結(jié)構(gòu)并將對(duì)象綁定到該層次結(jié)構(gòu)桌肴,如以下示例所示:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyLayoutBinding binding = MyLayoutBinding.inflate(getLayoutInflater());
}
除了LayoutInflater對(duì)象之外,inflate()
方法還有另一個(gè)版本琉历,它接受ViewGroup對(duì)象坠七,如下面的示例所示:
MyLayoutBinding binding = MyLayoutBinding.inflate(getLayoutInflater(), viewGroup, false);
如果使用不同的機(jī)制對(duì)布局進(jìn)行inflate水醋,則可以將其單獨(dú)綁定,如下所示:
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
有時(shí)無(wú)法預(yù)先知道綁定類(lèi)型彪置。在這種情況下拄踪,可以使用DataBindingUtil
類(lèi)創(chuàng)建綁定,如下面的代碼片段所示:
View viewRoot = LayoutInflater.from(this).inflate(layoutId, parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bind(viewRoot);
如果您在 Fragment
, ListView
, or RecyclerView
adapter,中使用數(shù)據(jù)綁定拳魁,您可能更喜歡使用bindings類(lèi)或DataBindingUtil
類(lèi)的inflate()
方法惶桐,如下面的代碼示例所示:
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
// or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
帶ID的視圖
數(shù)據(jù)綁定庫(kù)在綁定類(lèi)中為布局中具有ID的每個(gè)視圖創(chuàng)建一個(gè)不可變字段。例如潘懊,數(shù)據(jù)綁定庫(kù)從以下布局中創(chuàng)建TextView類(lèi)型的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>
該庫(kù)一次性從視圖層次結(jié)構(gòu)中提取包括id在內(nèi)的視圖姚糊。這種機(jī)制比為布局中的每個(gè)視圖調(diào)用findViewById()方法要快。
ID不是數(shù)據(jù)綁定的必要條件授舟,但是扔有些情況需要從代碼中訪問(wèn)視圖救恨。
變量
數(shù)據(jù)綁定庫(kù)為布局中聲明的每個(gè)變量生成訪問(wèn)器方法。例如释树,下面的布局在綁定類(lèi)中為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
對(duì)象一開(kāi)始是一個(gè)不可見(jiàn)的視圖。當(dāng)它們變得可見(jiàn)或被明確告知要inflate時(shí)奢啥,它們會(huì)通過(guò)inflate另一個(gè)布局來(lái)替換布局中的自己秸仙。
? 因?yàn)閂iewStub基本上從視圖層次結(jié)構(gòu)中消失,所以綁定對(duì)象中的視圖也必須消失桩盲,以便垃圾收集能夠會(huì)是它寂纪。因?yàn)橐晥D是最終的,所以一個(gè)ViewStubProxy對(duì)象在生成的綁定類(lèi)中代替了ViewStub赌结,當(dāng)ViewStub存在時(shí)捞蛋,您可以訪問(wèn)它,當(dāng)ViewStub inflated時(shí)姑曙,您還可以訪問(wèn)inflated視圖層次結(jié)構(gòu)襟交。
? 在inflating另一個(gè)布局時(shí)迈倍,必須為新布局建立綁定伤靠。因此,ViewStubProxy
必須監(jiān)聽(tīng)ViewStub``OnInflateListener
并在需要時(shí)建立綁定啼染。由于在給定的時(shí)間內(nèi)只能存在一個(gè)偵聽(tīng)器宴合,所以ViewStubProxy允許您設(shè)置一個(gè)OnInflateListener
,它在建立綁定之后調(diào)用這個(gè)OnInflateListener
迹鹅。
立即綁定
當(dāng)一個(gè)變量或可觀察對(duì)象發(fā)生變化時(shí)卦洽,數(shù)據(jù)綁定庫(kù)計(jì)劃在下一幀之前執(zhí)行綁定。然而斜棚,有時(shí)必須立即執(zhí)行綁定阀蒂。要強(qiáng)制執(zhí)行该窗,請(qǐng)使用executePendingBindings()
方法。
高級(jí)綁定
動(dòng)態(tài)變量
有時(shí)蚤霞,特定的綁定類(lèi)是未知的酗失。例如,RecyclerView.Adapter針對(duì)任意布局的操作不知道特定的綁定類(lèi)昧绣。它仍然必須在調(diào)用onBindViewHolder()方法期間分配綁定值规肴。
在以下示例中,RecyclerView
綁定的所有布局都具有 item
變量夜畴。該BindingHolder
對(duì)象有一個(gè)getBinding()
返回ViewDataBinding
基類(lèi)的方法 拖刃。
public void onBindViewHolder(BindingHolder holder, int position) {
final T item = items.get(position);
holder.getBinding().setVariable(BR.item, item);
holder.getBinding().executePendingBindings();
}
注意:數(shù)據(jù)綁定庫(kù)在模塊包中生成一個(gè)名為BR的類(lèi),在上面的示例中贪绘,庫(kù)自動(dòng)生成BR.item
變量兑牡。
后臺(tái)線程
您可以在后臺(tái)線程中更改除了集合以外的數(shù)據(jù)模型。數(shù)據(jù)綁定會(huì)在計(jì)算期間隔離每一個(gè)變量/字段以避免任何并發(fā)問(wèn)題
自定義綁定類(lèi)名稱(chēng)
默認(rèn)情況下兔簇,將根據(jù)布局文件的名稱(chēng)生成綁定類(lèi)发绢,以大寫(xiě)字母開(kāi)頭,刪除下劃線(_)垄琐,并大寫(xiě)后面一個(gè)字母边酒,且添加單詞Binding作為后綴。該類(lèi)放在 databinding
模塊包下的包中狸窘。例如墩朦,布局文件 contact_item.xml
生成ContactItemBinding
類(lèi)。如果模塊包是com.example.my.app
翻擒,則綁定類(lèi)放在 com.example.my.app.databinding
包中氓涣。
通過(guò)調(diào)整data
元素中的class
屬性,可以重命名綁定類(lèi)或?qū)⒔壎?lèi)放在不同的包中 陋气。例如劳吠,下面的布局在當(dāng)前模塊的databinding包中生成ContactItem綁定類(lèi):
<data class="ContactItem">
…
</data>
您可以通過(guò)在類(lèi)名前加一個(gè)句點(diǎn)在不同的包生成綁定類(lèi)。以下示例在模塊包中生成綁定類(lèi):
<data class=".ContactItem">
…
</data>
你可以使用完整包名在你想要的包中生成綁定類(lèi)巩趁。以下示例ContactItem
在com.example
包中創(chuàng)建綁定類(lèi) :
<data class="com.example.ContactItem">
…
</data>