導(dǎo)航:
- 搭建環(huán)境
- 數(shù)據(jù)綁定編譯器V2
- 數(shù)據(jù)綁定布局文件
- 布局細(xì)節(jié)
- 數(shù)據(jù)對象(重點)
- 生成綁定
- 屬性setters
- 轉(zhuǎn)換器
- Android Studio支持?jǐn)?shù)據(jù)綁定
本文檔解釋了如何使用數(shù)據(jù)綁定庫來編寫聲明式布局,并盡量減少綁定應(yīng)用程序邏輯和布局所需的膠合代碼。
數(shù)據(jù)綁定庫提供了靈活性和廣泛的兼容性 - 這是一個支持庫,所以你可以在Android 2.1(API級別7+)的所有Android平臺上使用它。
要使用數(shù)據(jù)綁定,Android Plugin for Gradle 1.5.0-alpha1 或更高版本是必需的。了解如何更新Android Plugin for Gradle。
1.配置環(huán)境
要開始使用數(shù)據(jù)綁定,請從Android SDK管理器的支持庫中下載庫。
要配置應(yīng)用程序以使用數(shù)據(jù)綁定,請將dataBinding 元素添加到應(yīng)用程序模塊中的build.gradle文件中。
使用下面的代碼片段來配置數(shù)據(jù)綁定:
android {
....
dataBinding {
enabled = true
}
}
如果您的應(yīng)用程序模塊依賴于使用數(shù)據(jù)綁定的庫,則您的應(yīng)用程序模塊也必須在其build.gradle 配置數(shù)據(jù)綁定。
另外,請確保您使用的是Android Studio的兼容版本。Android Studio 1.3及更高版本支持?jǐn)?shù)據(jù)綁定,如Android Studio支持?jǐn)?shù)據(jù)綁定中所述。
2.數(shù)據(jù)綁定編譯器V2
Android Gradle插件3.1.0 Canary 6附帶一個可選的新編譯器。要開始使用它,請更新您的gradle.properties文件以包含以下行:
android.databinding.enableV2=true
在編譯器v2中:
-
ViewBinding類是在java編譯器之前由Android Gradle Plugin生成的。這可以避免由于不相關(guān)的原因?qū)е耲ava編譯失敗而導(dǎo)致太多的錯誤肯定錯誤。 - 在V1中,編譯應(yīng)用程序時會重新生成庫的綁定類(以共享生成的代碼并訪問最終的“BR”和“R”文件)。在V2中,庫保持其生成的綁定類以及映射器信息,這為多模塊項目顯著提高了數(shù)據(jù)綁定性能。
請注意,這個新的編譯器是向后不兼容的,所以用v1編譯的庫不能被v2使用,反之亦然。
V2還會刪除一些很少使用的功能來允許這些更改:
- 在V1中,一個應(yīng)用程序能夠提供綁定適配器,可以覆蓋依賴項中的適配器。在V2中,它只對自己的模塊/應(yīng)用程序及其依賴項中的代碼生效。
- 以前,如果一個布局文件在兩個或多個不同的資源配置中包含一個
View具有相同id但不同類的數(shù)據(jù),則數(shù)據(jù)綁定將查找最常見的父類。View當(dāng)配置之間的類型不匹配時,它將始終默認(rèn)為。 - 在V2中,不同的模塊不能在清單中使用相同的包名稱,因為數(shù)據(jù)綁定將使用該包名來生成綁定映射器類。
3.數(shù)據(jù)綁定布局文件
3.1編寫你的第一套數(shù)據(jù)綁定表達(dá)式
數(shù)據(jù)綁定布局文件稍有不同,layout作為根標(biāo)簽,后跟data 元素和 view根元素。示例文件如下所示:
<?xml version="1.0" encoding="utf-8"?>
<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}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"/>
</LinearLayout>
</layout>
data標(biāo)簽中的user變量,描述了可以在布局中使用的屬性。
<variable name="user" type="com.example.User"/>
布局中的使用@{}語法表達(dá)式寫入屬性。在這里,TextView的文本被設(shè)置為用戶的firstName屬性:
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
Data Object
3.2 數(shù)據(jù)對象
現(xiàn)在讓我們假設(shè)你有一個普通的Java對象(PO??JO)用戶:
public class User {
public final String firstName;
public final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
這種類型的對象的數(shù)據(jù)永遠(yuǎn)不會改變。在應(yīng)用程序中通常會讀取一次數(shù)據(jù),之后再也不會更改。也可以使用JavaBeans對象:
public class User {
private final String firstName;
private final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return this.firstName;
}
public String getLastName() {
return this.lastName;
}
}
從數(shù)據(jù)綁定的角度來看,這兩個類是等價的。@{user.firstName}用于TextView android:text屬性的表達(dá)式將訪問前一類中的firstName字段和后一類中的getFirstName()方法?;蛘撸绻?code>firstName()方法存在,也將被解析。
3.3 綁定數(shù)據(jù)
默認(rèn)情況下,將根據(jù)布局文件的名稱生成一個Binding類,將其轉(zhuǎn)換為Pascal格式并將Binding后綴添加到該文件中。上面的布局文件是main_activity.xml這樣的生成類是MainActivityBinding。這個類將布局屬性(例如user變量)的所有綁定保存到布局的視圖中,并知道如何為綁定表達(dá)式賦值。創(chuàng)建綁定的最簡單方法是在inflating時進(jìn)行:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
User user = new User("Test", "User");
binding.setUser(user);
}
你完成了!運行應(yīng)用程序,你會看到用戶界面中的測試user。或者,您可以通過以下方式獲取視圖:
MainActivityBinding binding =
MainActivityBinding.inflate(getLayoutInflater());
如果您在ListView或RecyclerView適配器內(nèi)使用數(shù)據(jù)綁定項目,則可能更愿意使用:
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
Event Handling
3.4 事件處理
數(shù)據(jù)綁定允許您編寫表達(dá)式來處理從視圖中分派的事件(例如onClick)。除少數(shù)例外,事件屬性名稱由偵聽器方法的名稱來管理。View.OnLongClickListener 有一個方法 onLongClick(),所以這個事件的屬性是android:onLongClick。處理事件有兩種方法。
- 方法引用:在你的表達(dá)式中,你可以引用符合偵聽器方法簽名的方法。當(dāng)表達(dá)式評估為方法引用時,數(shù)據(jù)綁定將方法引用和所有者對象包裝在偵聽器中,并將該偵聽器設(shè)置在目標(biāo)視圖上。如果表達(dá)式求值為null,則數(shù)據(jù)綁定不會創(chuàng)建偵聽器,而是設(shè)置空偵聽器。
- 監(jiān)聽器綁定:這些是在事件發(fā)生時被評估的lambda表達(dá)式。數(shù)據(jù)綁定總是創(chuàng)建一個監(jiān)聽器,它在視圖上設(shè)置。事件發(fā)送時,監(jiān)聽器評估lambda表達(dá)式。
3.4.1方法引用
事件可以直接綁定到處理方法,類似于 android:onClick可以分配給Activity中的方法。與View#onClick屬性相比,一個主要的優(yōu)點是表達(dá)式在編譯時被處理,所以如果方法不存在或者它的簽名不正確,你會收到一個編譯時錯誤。
方法引用和監(jiān)聽器綁定的主要區(qū)別在于實際的監(jiān)聽器實現(xiàn)是在綁定數(shù)據(jù)時創(chuàng)建的,而不是在事件觸發(fā)時創(chuàng)建的。如果您喜歡在事件發(fā)生時評估表達(dá)式,則應(yīng)該使用監(jiān)聽器綁定。
要將事件分配給其處理程序,請使用常規(guī)綁定表達(dá)式,其值是要調(diào)用的方法名稱。例如,如果你的數(shù)據(jù)對象有兩個方法:
public class MyHandlers {
public void onClickFriend(View view) { ... }
}
綁定表達(dá)式可以為View分配一個點擊監(jiān)聽器:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="handlers" type="com.example.MyHandlers"/>
<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:onClick="@{handlers::onClickFriend}"/>
</LinearLayout>
</layout>
請注意,表達(dá)式中方法的簽名必須與Listener對象中方法的簽名完全匹配
3.4.2監(jiān)聽器綁定
監(jiān)聽器綁定是事件發(fā)生時運行的綁定表達(dá)式。它們類似于方法引用,但是它們允許您運行任意的數(shù)據(jù)綁定表達(dá)式。此功能適用于Gradle 2.0版及更高版本的Android Gradle插件。
在方法引用中,方法的參數(shù)必須與事件偵聽器的參數(shù)匹配。在監(jiān)聽器綁定中,只有你的返回值必須與監(jiān)聽器的期望返回值相匹配(除非它預(yù)期為void)。例如,您可以有一個具有以下方法的演示者類:
public class Presenter {
public void onSaveClick(Task task){}
}
然后你可以如下綁定點擊事件:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="task" type="com.android.example.Task" />
<variable name="presenter" type="com.android.example.Presenter" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{() -> presenter.onSaveClick(task)}" />
</LinearLayout>
</layout>
監(jiān)聽器僅允許只允許由lambda表達(dá)式作為根元素的表達(dá)式。在表達(dá)式中使用回調(diào)函數(shù)時,數(shù)據(jù)綁定會自動為事件創(chuàng)建必要的偵聽器和注冊表。當(dāng)視圖觸發(fā)事件時,數(shù)據(jù)綁定將評估給定的表達(dá)式。就像在常規(guī)的綁定表達(dá)式中一樣,當(dāng)這些監(jiān)聽器表達(dá)式被評估的時候,你仍然可以獲得數(shù)據(jù)綁定的null和線程安全性。
請注意,在上面的例子中,我們沒有定義view傳入?yún)?shù)onClick(android.view.View)。監(jiān)聽器綁定為監(jiān)聽器參數(shù)提供了兩個選擇:您可以忽略該方法的所有參數(shù)或?qū)⑵淙棵?。如果您想要命名參?shù),則可以在表達(dá)式中使用它們。例如,上面的表達(dá)式可以寫成:
android:onClick="@{(view) -> presenter.onSaveClick(task)}"
或者如果你想使用表達(dá)式中的參數(shù),它可以如下工作:
public class Presenter {
public void onSaveClick(View view, Task task){}
}
---
android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"
您可以使用多于一個參數(shù)的lambda表達(dá)式:
public class Presenter {
public void onCompletedChanged(Task task, boolean completed){}
}
------
<CheckBox android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onCheckedChanged="@{(cb, isChecked) ->
presenter.completeChanged(task, isChecked)}" />
如果正在偵聽的事件返回一個其類型不是void的值,則表達(dá)式必須返回相同類型的值。例如,如果要監(jiān)聽長按事件,則應(yīng)該返回表達(dá)式boolean。
public class Presenter {
public boolean onLongClick(View view, Task task){}
}
--------
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"
如果由于是null對象而無法評估表達(dá)式,Data Binding將返回該類型的默認(rèn)Java值。例如,null返回引用類型,0返回 int, false返回 boolean等。
如果您需要使用謂詞(例如三元)表達(dá)式,則可以將 void用作符號。
android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"
避免復(fù)雜的監(jiān)聽
監(jiān)聽器表達(dá)式非常強(qiáng)大,可以讓你的代碼非常容易閱讀。另一方面,包含復(fù)雜表達(dá)式的監(jiān)聽會使您的布局難以閱讀和維護(hù)。這些表達(dá)式應(yīng)該像從UI中傳遞可用數(shù)據(jù)到回調(diào)方法一樣簡單。您應(yīng)該在您從偵聽器表達(dá)式調(diào)用的回調(diào)方法內(nèi)實現(xiàn)任何業(yè)務(wù)邏輯。
存在一些專門的單擊事件處理程序,它們需要一個屬性, android:onClick以避免沖突。已經(jīng)創(chuàng)建了以下屬性以避免這種沖突:
| class | Listener Setter | Attribute |
|---|---|---|
| SearchView | setOnSearchClickListener(View.OnClickListener) | android:onSearchClick |
| ZoomControls | setOnZoomInClickListener(View.OnClickListener) | android:onZoomIn |
| ZoomControls | setOnZoomOutClickListener(View.OnClickListener) | android:onZoomOut |
4.布局細(xì)節(jié)
4.1 import
data元素中可以使用零個或多個import元素,這些就像在Java中一樣可以輕松地引用布局文件中的類。
<data>
<import type="android.view.View"/>
</data>
現(xiàn)在,可以在你的綁定表達(dá)式中使用視圖:
<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
當(dāng)有類名沖突時,其中一個類可能會被重命名為“alias:”
<import type="android.view.View"/>
<import type="com.example.real.estate.View"
alias="Vista"/>
現(xiàn)在,Vista可能會以com.example.real.estate.View,View可能以android.view.View在布局文件內(nèi)引用。導(dǎo)入的類型可以用作變量和表達(dá)式中的類型引用:
<data>
<import type="com.example.User"/>
<import type="java.util.List"/>
<variable name="user" type="User"/>
<variable name="userList" type="List<User>"/>
</data>
注意:Android Studio尚未處理導(dǎo)入,因此導(dǎo)入變量的自動填充可能無法在您的IDE中工作。您的應(yīng)用程序仍然可以正常編譯,您可以通過在變量定義中使用完全限定的名稱來解決IDE問題。
<TextView
android:text="@{((User)(user.connection)).lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
當(dāng)在表達(dá)式中引用靜態(tài)字段和方法時,也可以使用導(dǎo)入的類型:
<data>
<import type="com.example.MyStringUtils"/>
<variable name="user" type="com.example.User"/>
</data>
…
<TextView
android:text="@{MyStringUtils.capitalize(user.lastName)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
就像在Java中一樣,java.lang.*自動導(dǎo)入。
4.2 Variables
data元素內(nèi)可以使用任意數(shù)量的variable元素,每個variable元素描述可以在布局上設(shè)置的屬性,以用于布局文件中的綁定表達(dá)式。
<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>
變量類型在編譯時被檢查,所以如果一個變量實現(xiàn)Observable或者是一個observable collection,那么這個類型應(yīng)該被反映出來。如果變量是不實現(xiàn)Observable接口的基類或接口,將不會觀察變量!
當(dāng)不同的配置文件(例如橫向或縱向)有不同的布局文件時,變量將被合并。這些布局文件之間不得存在沖突的變量定義。
生成的綁定類將為每個描述的變量設(shè)置一個setter和getter。變量將采用默認(rèn)的Java值,直到調(diào)用者被調(diào)用 - null是引用類型,0是 int,false是boolean等。
根據(jù)需要生成一個名為context的特殊變量用于綁定表達(dá)式。context是視圖的getContext()得到的Context。context變量將被具有該名稱的顯式變量聲明覆蓋。
4.3自定義綁定類名稱
默認(rèn)情況下,根據(jù)布局文件的名稱生成一個Binding類,以大寫字母開頭,刪除下劃線(_)并大寫下面的字母,然后后綴“Binding”。這個類將被放置在模塊包下的數(shù)據(jù)綁定包中。例如,布局文件contact_item.xml將生成 ContactItemBinding。如果模塊包是 com.example.my.app,那么它將被放入com.example.my.app.databinding。
綁定類可以通過調(diào)整class元素的屬性來重命名或放置data在不同的包中。
<data class="ContactItem">
...
</data>
這將在模塊包中的數(shù)據(jù)綁定包中生成綁定類ContactItem。如果該類應(yīng)該在模塊包中的其他包中生成,則可以用.作為前綴:
<data class=".ContactItem">
...
</data>
在這種情況下,ContactItem直接在模塊包中生成。如果提供完整的軟件包,則可以在任何軟件包中:
<data class="com.example.ContactItem">
...
</data>
4.4 Includes
通過使用應(yīng)用程序名稱空間和屬性的變量名,變量可以被傳遞到包含布局的包含綁定中:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/name"
bind:user="@{user}"/>
<include layout="@layout/contact"
bind:user="@{user}"/>
</LinearLayout>
</layout>
在這里,name.xml和contact.xml布局文件中都必須有一個user變量。
數(shù)據(jù)綁定不支持include作為merge元素的直接子元素。例如,不支持以下布局:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable name="user" type="com.example.User"/>
</data>
<merge>
<include layout="@layout/name"
bind:user="@{user}"/>
<include layout="@layout/contact"
bind:user="@{user}"/>
</merge>
</layout>
4.5 1. 表達(dá)式語言
共同特征
表達(dá)式語言看起來很像Java表達(dá)式。這些是一樣的:
- 數(shù)學(xué)的:
+ - / * % - 字符串連接:
+ - 邏輯:
&& || - 二進(jìn)制:
& | ^ - 一元:
+ - ! ~ - 轉(zhuǎn)移:
>> >>> << - 對照:
== > < >= <= instanceof- 分組:
() - 文字 - 字符,字符串,數(shù)字,
null - Cast
- 方法調(diào)用
- 字段訪問
- 數(shù)組訪問
[] - 三元操作符
?:
例子:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}
不支持的操作
您可以在Java中使用的表達(dá)式語法中缺少一些操作。
thissupernew- 明確的泛型調(diào)用
空合并運算符
null合并運算符(??)選擇左操作數(shù)(如果不是null)或右(如果為空)。
android:text="@{user.displayName ?? user.lastName}"
這在功能上等同于:
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
屬性引用
第一個已經(jīng)在上面的編寫第一個數(shù)據(jù)綁定表達(dá)式中討論過了:簡短形式的JavaBean引用。當(dāng)一個表達(dá)式引用一個類的屬性時,它對字段,獲取器和ObservableFields使用相同的格式。
android:text="@{user.lastName}"
避免空指針異常
生成的數(shù)據(jù)綁定代碼自動檢查空值并避免空指針異常。例如,在 @{user.name}表達(dá)式中如果user為null, user.name將被賦予其默認(rèn)值(null)。如果你是引用user.age,年齡是一個int,那么它將默認(rèn)為0。
集合
公共集合:數(shù)組,列表,稀疏列表和地圖,為了方便可以使用[]操作符。
<data>
<import type="android.util.SparseArray"/>
<import type="java.util.Map"/>
<import type="java.util.List"/>
<variable name="list" type="List<String>"/>
<variable name="sparse" type="SparseArray<String>"/>
<variable name="map" type="Map<String, String>"/>
<variable name="index" type="int"/>
<variable name="key" type="String"/>
</data>
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"
字符串文字
在屬性值周圍使用單引號時,在表達(dá)式中使用雙引號很容易:
android:text='@{map["firstName"]}'
也可以使用雙引號來包圍屬性值。這樣做時,字符串文字應(yīng)該使用'或者反引號( ` )。
android:text="@{map[`firstName`}"
android:text="@{map['firstName']}"
資源
使用正常語法可以將資源作為表達(dá)式的一部分進(jìn)行訪問:
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
格式字符串和復(fù)數(shù)可以通過提供參數(shù)來評估:
android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"
當(dāng)一個復(fù)數(shù)有多個參數(shù)時,所有參數(shù)都應(yīng)該傳遞:
Have an orange
Have %d oranges
android:text="@{@plurals/orange(orangeCount, orangeCount)}"
有些資源需要明確的類型評估。
| Type | Normal Reference | Expression Reference |
|---|---|---|
| String[] | @array | @stringArray |
| int[] | @array | @intArray |
| TypedArray | @array | @typedArray |
| Animator | @animator | @animator |
| StateListAnimator | @animator | @stateListAnimator |
| color int | @color | @color |
| ColorStateList | @color | @colorStateList |
5.數(shù)據(jù)對象
任何普通的舊Java對象(PO??JO)都可以用于數(shù)據(jù)綁定,但修改POJO不會導(dǎo)致UI更新。數(shù)據(jù)綁定的真正威力可以通過給你的數(shù)據(jù)對象提供在數(shù)據(jù)改變時通知的能力。有三種不同的數(shù)據(jù)更改通知機(jī)制: 可觀察對象, 可觀察字段和 可觀察集合。
當(dāng)這些可觀察的數(shù)據(jù)對象之一被綁定到UI并且數(shù)據(jù)對象的屬性改變時,UI將被自動更新。
5.1可觀察的對象
實現(xiàn)Observable接口的類將允許綁定將單個偵聽器附加到綁定對象,以偵聽該對象上所有屬性的更改。
Observable接口具有添加和刪除偵聽器的機(jī)制,但通知由開發(fā)人員決定。為了簡化開發(fā),創(chuàng)建了一個BaseObservable基類來實現(xiàn)監(jiān)聽器注冊機(jī)制。數(shù)據(jù)類實現(xiàn)者仍然負(fù)責(zé)通知屬性何時更改,通過分配一個Bindable注釋給getter并且在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類文件將在模塊包中生成。如果數(shù)據(jù)類的基類不能改變,那么Observable將會被實現(xiàn),通過使用PropertyChangeRegistry去存儲和有效地通知偵聽器。
5.2 可觀察字段
Observable類大有內(nèi)涵,所以開發(fā)者想要節(jié)省時間和添加有幾個屬性,可以使用ObservableField和它的兄弟姐妹
ObservableBoolean,ObservableByte,ObservableChar,ObservableShort,ObservableInt,ObservableLong,ObservableFloat,ObservableDouble,和ObservableParcelable。
ObservableFields是具有單個字段的自包含可觀察對象。原始版本在訪問操作期間避免裝箱和取消裝箱。要使用,請在數(shù)據(jù)類中創(chuàng)建一個公共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();
}
就這么簡單!要訪問該值,請使用set和get訪問器方法:
user.firstName.set("Google");
int age = user.age.get();
5.3 可觀察的集合
一些應(yīng)用程序使用更多的動態(tài)結(jié)構(gòu)來保存數(shù)據(jù),可觀察集合允許對這些數(shù)據(jù)對象進(jìn)行鍵控訪問。當(dāng)鍵是引用類型(如String)時ObservableArrayMap非常有用。
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
在布局中,可以通過String鍵訪問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"/>
當(dāng)鍵是一個整數(shù)時使用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"/>
6.生成的綁定
生成的綁定類將布局變量與布局中的視圖鏈接起來。如前所述,綁定的名稱和包可能是 自定義的。生成的綁定類全部擴(kuò)展ViewDataBinding。
6.1創(chuàng)建
應(yīng)該在inflation之后立即創(chuàng)建綁定,以確保在綁定到布局中帶有表達(dá)式的視圖之前,View層次結(jié)構(gòu)不受干擾。有幾種方法可以綁定到布局。最常見的是在Binding類中使用靜態(tài)方法。inflate方法inflate了View層次結(jié)構(gòu),并將其一步綁定。有一個簡單的版本,只需要一個LayoutInflater和ViewGroup:
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);
如果布局使用不同的inflate機(jī)制,它可能會被分開綁定:
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
有時綁定不能預(yù)先知道。在這種情況下,綁定可以使用DataBindingUtil類創(chuàng)建:
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);
6.2帶有ID的視圖
每個帶有ID的視圖將在布局中生成一個對應(yīng)的公開fianl字段。該綁定在View層次結(jié)構(gòu)上執(zhí)行單個傳遞,提取帶有ID的視圖。這個機(jī)制可以比調(diào)用多個視圖的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在數(shù)據(jù)綁定時不是必須的,但是仍然有一些情況下代碼仍然需要訪問視圖。
6.3變量
每個變量將被賦予訪問器方法。
<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>
將在綁定中生成setter和getters:
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);
6.4ViewStubs
ViewStub與正常view有些不同。他們從不可見的時候開始,當(dāng)他們要么變得可見時,或者被明確地告知inflate時,他們通過inflate另一種布局來取代布局。
由于ViewStub本質(zhì)上從View層次消失,綁定對象中的View也必須消失以允許收集。因為view是最終的,所以一個ViewStubProxy對象代替了這個ViewStub視圖,當(dāng)開發(fā)者存在的時候,它允許開發(fā)人員訪問ViewStub,并且在ViewStub被inflate時也可以訪問inflate的View層次結(jié)構(gòu)。
當(dāng)inflate另一個布局時,必須為新的布局建立綁定。因此,ViewStubProxy一定監(jiān)聽ViewStub的ViewStub.OnInflateListener,同時建立綁定。由于只有一個可以存在,所以ViewStubProxy允許開發(fā)者設(shè)置一個OnInflateListener,在建立綁定之后它將被調(diào)用
6.5高級綁定
動態(tài)變量
有時,特定的綁定類是不知道的,例如,RecyclerView.Adapter針對任意布局的操作將不知道具體的綁定類。它仍然必須分配綁定值onBindViewHolder(VH, int)。
在這個例子中,RecyclerView綁定的所有布局都有一個“item”變量。所述的BindingHolder具有getBinding方法返回基礎(chǔ)的ViewDataBinding。
public void onBindViewHolder(BindingHolder holder, int position) {
final T item = mItems.get(position);
holder.getBinding().setVariable(BR.item, item);
holder.getBinding().executePendingBindings();
}
6.6 立即綁定
當(dāng)變量或可觀察對象變化時,綁定將被安排在下一幀之前改變。但有時候,綁定必須立即執(zhí)行。要強(qiáng)制執(zhí)行,請使用該executePendingBindings()方法。
6.7后臺線程
只要不是集合,就可以在后臺線程中更改數(shù)據(jù)模型。數(shù)據(jù)綁定將在評估時本地化每個變量/字段,以避免任何并發(fā)問題。
7. 屬性Setters
每當(dāng)綁定值發(fā)生變化時,生成的綁定類必須使用綁定表達(dá)式在視圖上調(diào)用setter方法。數(shù)據(jù)綁定框架可以自定義調(diào)用哪個方法來設(shè)置值。
7.1 自動 Setters
對于一個屬性,數(shù)據(jù)綁定試圖找到方法setAttribute。屬性的命名空間并不重要,只有屬性名稱本身才是重點。
例如,與TextView屬性關(guān)聯(lián)的表達(dá)式 android:text將查找setText(String)。如果表達(dá)式返回一個int,那么數(shù)據(jù)綁定將搜索一個setText(int)方法。請注意讓表達(dá)式返回正確的類型,如果需要的話就進(jìn)行轉(zhuǎn)換。請注意,即使給定名稱不存在任何屬性,數(shù)據(jù)綁定也可以工作。然后,您可以使用數(shù)據(jù)綁定輕松地為任何‘創(chuàng)建’的屬性進(jìn)行setter。例如,support DrawerLayout沒有任何屬性,但是有很多setter。您可以使用自動設(shè)置器來使用其中的一個。
<android.support.v4.widget.DrawerLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:scrimColor="@{@color/scrim}"
app:drawerListener="@{fragment.drawerListener}"/>
7.2重命名Setters
一些擁有setters方法的屬性與名稱不符合。對于這些方法,屬性可能通過BindingMethods注釋與setter相關(guān)聯(lián)。這必須與一個類相關(guān)聯(lián),并包含BindingMethod注釋,每個重命名的方法一個。例如,android:tint屬性確實與setImageTintList(ColorStateList)關(guān)聯(lián),而不是 setTint。
@BindingMethods({
@BindingMethod(type = "android.widget.ImageView",
attribute = "android:tint",
method = "setImageTintList"),
})
開發(fā)人員不太可能需要重命名setter;android框架的屬性已經(jīng)實現(xiàn)了。
7.3自定義setters
一些屬性需要自定義綁定邏輯。例如,android:paddingLeft 屬性沒有關(guān)聯(lián)的setter。相反,setPadding(left, top, right, bottom)存在。帶BindingAdapter注釋的靜態(tài)綁定適配器方法,允許開發(fā)人員定制如何調(diào)用屬性的setter。
Android屬性已經(jīng)創(chuàng)建BindingAdapter。例如,這里是一個用于paddingLeft:
@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
綁定適配器對其他類型的自定義非常有用。例如,一個自定義的加載器可以被調(diào)用脫機(jī)線程來加載一個圖像。
當(dāng)發(fā)生沖突時,開發(fā)人員創(chuàng)建的綁定適配器將覆蓋數(shù)據(jù)綁定默認(rèn)適配器。
您也可以讓適配器接收多個參數(shù)。
@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}"/>
如果imageUrl和 error都用于ImageView且imageUrl是字符串,并且error是drawable,則將調(diào)用此適配器。
- 自定義名稱空間在匹配過程中被忽略。
- 您也可以為android命名空間編寫適配器。
綁定適配器方法可以選擇性的在其處理程序中使用舊值。采用新舊價值的方法,首先應(yīng)該擁有屬性的舊值,其次是新的值:
@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int oldPadding, int newPadding) {
if (oldPadding != newPadding) {
view.setPadding(newPadding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
}
事件處理程序只能用于接口或一個有抽象方法的抽象類。例如:
@BindingAdapter("android:onLayoutChange")
public static void setOnLayoutChangeListener(View view, View.OnLayoutChangeListener oldValue,
View.OnLayoutChangeListener newValue) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
if (oldValue != null) {
view.removeOnLayoutChangeListener(oldValue);
}
if (newValue != null) {
view.addOnLayoutChangeListener(newValue);
}
}
}
當(dāng)一個監(jiān)聽器有多個方法時,它必須被分成多個監(jiān)聽器。比如View.OnAttachStateChangeListener有兩種方法:onViewAttachedToWindow()和onViewDetachedFromWindow()。然后我們必須創(chuàng)建兩個接口來區(qū)分它們的屬性和處理程序。
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewDetachedFromWindow {
void onViewDetachedFromWindow(View v);
}
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewAttachedToWindow {
void onViewAttachedToWindow(View v);
}
因為更改一個偵聽器也會影響另一個偵聽器,所以我們必須有三個不同的綁定適配器,一個用于每個屬性,另一個用于兩個,如果它們都被設(shè)置。
@BindingAdapter("android:onViewAttachedToWindow")
public static void setListener(View view, OnViewAttachedToWindow attached) {
setListener(view, null, attached);
}
@BindingAdapter("android:onViewDetachedFromWindow")
public static void setListener(View view, OnViewDetachedFromWindow detached) {
setListener(view, detached, null);
}
@BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"})
public static void setListener(View view, final OnViewDetachedFromWindow detach,
final OnViewAttachedToWindow attach) {
if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) {
final OnAttachStateChangeListener newListener;
if (detach == null && attach == null) {
newListener = null;
} else {
newListener = new OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
if (attach != null) {
attach.onViewAttachedToWindow(v);
}
}
@Override
public void onViewDetachedFromWindow(View v) {
if (detach != null) {
detach.onViewDetachedFromWindow(v);
}
}
};
}
final OnAttachStateChangeListener oldListener = ListenerUtil.trackListener(view,
newListener, R.id.onAttachStateChangeListener);
if (oldListener != null) {
view.removeOnAttachStateChangeListener(oldListener);
}
if (newListener != null) {
view.addOnAttachStateChangeListener(newListener);
}
}
}
上面的例子比正常情況稍微復(fù)雜,因為View使用add和remove來代替View.OnAttachStateChangeListener的set方法android.databinding.adapters.ListenerUtil類可以幫助跟蹤以前的監(jiān)聽,讓他們可以在綁定Adaper中被刪除的。
通過用@TargetApi(VERSION_CODES.HONEYCOMB_MR1)注釋OnViewDetachedFromWindow和OnViewAttachedToWindow接口,數(shù)據(jù)綁定代碼生成器知道監(jiān)聽器只在Honeycomb MR1和新設(shè)備生成;同樣的情況在addOnAttachStateChangeListener(View.OnAttachStateChangeListener)上發(fā)生。
8. 轉(zhuǎn)換器
8.1對象轉(zhuǎn)換
從綁定表達(dá)式返回一個對象時,將從自動,重命名和自定義setter中選擇一個setter。該對象將被轉(zhuǎn)換為所選setter的參數(shù)類型。
這對于那些使用ObservableMaps來保存數(shù)據(jù)的人來說非常方便。例如:
<TextView
android:text='@{userMap["lastName"]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
將返回一個userMap對象,并且將對象自動轉(zhuǎn)換為setText(CharSequence)中的參數(shù)類型。當(dāng)參數(shù)類型可能混淆時,開發(fā)人員需要在表達(dá)式中輸入。
8.2 自定義轉(zhuǎn)換
有時轉(zhuǎn)換應(yīng)該在特定類型之間自動進(jìn)行。例如,設(shè)置背景時:
<View
android:background="@{isError ? @color/red : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
在這里,背景需要一個Drawable,但顏色是一個integer。不管期望 Drawable或是返回一個integer,int應(yīng)該被轉(zhuǎn)換成一個ColorDrawable。這個轉(zhuǎn)換是通過一個帶有BindingConversion注解的靜態(tài)方法完成的:
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}
請注意,轉(zhuǎn)換只發(fā)生在setter級別,所以不允許混合類型如下所示:
<View
android:background="@{isError ? @drawable/error : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
9. Android Studio支持?jǐn)?shù)據(jù)綁定
Android Studio支持?jǐn)?shù)據(jù)綁定代碼的許多代碼編輯功能。例如,它支持?jǐn)?shù)據(jù)綁定表達(dá)式的以下功能:
注意:數(shù)組和 泛型類型(如Observable類)可能會在沒有錯誤時,顯示錯誤。
預(yù)覽窗格顯示數(shù)據(jù)綁定表達(dá)式的默認(rèn)值(如果提供)。在下面的示例摘錄了布局XML文件中的元素后,“預(yù)覽”窗口將在TextView中顯示PLACEHOLDER默認(rèn)的文本值。
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName, default=PLACEHOLDER}"/>
如果需要在項目設(shè)計階段顯示默認(rèn)值,則還可以使用工具屬性而不是默認(rèn)表達(dá)式值,如 Design Time Layout Attributes中所述。