Data Binding 類庫(kù)
這篇文檔將教你如何運(yùn)用 Data Binding 類庫(kù)來(lái)編寫聲明試布局葱绒,并且盡量減少粘合代碼對(duì)你的應(yīng)用邏輯和布局上的綁定失球。
Data Binding 是一種靈活和廣泛兼容的類庫(kù),它是一個(gè)支持庫(kù)黔牵,因此你可以在任何 Android 2.1(API level 7+) 以上的設(shè)備 使用。
為了使用 Data Binding,Android Gradle 插件版本必須為 1.5.0-alpha1 或以上牧愁,查看 如何升級(jí)你的 Gradle 插件。
構(gòu)建環(huán)境
為了獲取 Data Binding磨确,去 Android SDK manager 下載 它的支持庫(kù)。
在你的應(yīng)用 module 的 build.gradle
添加 dataBinding 來(lái)讓你的應(yīng)用支持 Data Binding邓了。
用以下代碼片段來(lái)配置 Data Binding:
android {
....
dataBinding {
enabled = true
}
}
若你有一個(gè)應(yīng)用 module 用了一個(gè)依賴了 Data Binding 的類庫(kù)照宝,也一樣要在該 module 中配置開啟 Data Binding厕鹃。
另外,如果想使用 Data Binding汗茄,你們你的 Android Studio 版本必須等于或大于 1.3。
Data Binding 布局文件
編寫你的第一個(gè) Data Binding 表達(dá)式
Data Binding 的布局文件有一點(diǎn)不一樣瞳腌,它以 layout
標(biāo)簽作為根標(biāo)簽,并且有一個(gè)data
元素和 一個(gè) view
元素作為子標(biāo)簽挑宠,這個(gè) view
元素就是你沒有使用 Data Binding 時(shí)該有的布局文件。以下是一個(gè)例子:
<?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)簽下的 variable
是你在這個(gè) Data Binding 布局文件中有可能使用到的對(duì)象碎浇。
<variable name="user" type="com.example.User"/>
布局中使用 @{}
語(yǔ)法來(lái)包裹 variable
中的對(duì)象屬性,在下面例子中苟穆,TextView
的 text
屬性的值用 user
的 firstName
屬性來(lái)替代剖膳。
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
數(shù)據(jù)對(duì)象
現(xiàn)在讓我們假設(shè)你有一個(gè)普通的 Java 對(duì)象(POJO)User
:
public class User {
public final String firstName;
public final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
這個(gè)對(duì)象屬性(final 修飾)是不可變的,如果你的數(shù)據(jù)對(duì)象只提供只讀權(quán)限并且之后不會(huì)再去修改的話仑濒,這種做法很普遍。我們也可以用 JavaBeans 對(duì)象來(lái)表示:
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ù)綁定的角度來(lái)看喉酌,這兩個(gè)類是等價(jià)的。TextView
的 android:text
屬性值會(huì)通過表達(dá)式 @{user.firstName}
來(lái)獲取第一個(gè)類中的 fistName
字段值相速,活著獲取第二個(gè)類中的 getFirstName()
方法返回的值。另外旺隙,如果 firstName()
方法存在的話也是可以獲取到值的。
綁定數(shù)據(jù)
默認(rèn)情況下,將根據(jù)布局文件的名稱生成一個(gè)綁定類摘昌,將其轉(zhuǎn)換為 Pascal 格式并將 Binding
作為其后綴。上面的布局文件是名稱
main_activity.xml
锦秒,因此生成的綁定類是 MainActivityBinding
旅择。這個(gè)類將布局屬性(例如用戶變量)綁定到布局的視圖中,并知道如何通過表達(dá)式來(lái)賦值柱蟀。創(chuàng)建綁定類的最簡(jiǎn)單方式是在視圖 inflate 的時(shí)候:
@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ùn)行這個(gè)應(yīng)用术瓮,你會(huì)在界面中看到測(cè)試的 User
。另外撬讽,你可以通過一些方
式獲取綁定類:
MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());
如果你在 ListView
或者 RecyclerView
中使用數(shù)據(jù)綁定電話,你可以通過一些方式獲取綁定類:
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
事件處理
數(shù)據(jù)綁定允許你編寫表達(dá)式來(lái)處理從視圖中分派的事件(例如 onClick
)烘豌。除少數(shù)例外,事件屬性名稱由偵聽器中的方法名稱來(lái)確定标锄。例如料皇,View.OnLongClickListener
有一個(gè) onLongClick()
方法,所以這個(gè)事件的屬性是 android:onLongClick
优质。有以下兩種方式來(lái)處理一個(gè)事件盆赤。
- 方法引用:在表達(dá)式中,可以引用符合偵聽器方法簽名的方法淑际。 當(dāng)表達(dá)式被評(píng)估為方法引用時(shí),數(shù)據(jù)綁定將方法引用和所有者對(duì)象包裝在偵聽器中锄贼,并將該偵聽器設(shè)置在目標(biāo)視圖上。 如果表達(dá)式被評(píng)估為 null冯键,則數(shù)據(jù)綁定不會(huì)創(chuàng)建偵聽器,而是設(shè)置一個(gè)空的偵聽器改化。
- 監(jiān)聽器綁定:當(dāng)事件發(fā)生時(shí),lambda 表達(dá)式將被評(píng)估燥爷。 數(shù)據(jù)綁定總是會(huì)在視圖上創(chuàng)建一個(gè)監(jiān)聽器前翎。 當(dāng)事件被發(fā)送時(shí),監(jiān)聽器將評(píng)估 lambda 表達(dá)式立宜。
方法引用
事件可以直接綁定到處理的方法中,類似于 android:onClick
可以作為 Activity 的一個(gè)方法一樣灯帮。與 View#onClick
屬性相比,一個(gè)主要的優(yōu)點(diǎn)是表達(dá)式在編譯時(shí)被處理腻贰,因此如果方法不存在或者它的簽名不正確,就會(huì)收到編譯時(shí)錯(cuò)誤宾巍。
方法引用和監(jiān)聽器綁定的主要區(qū)別在于實(shí)際的監(jiān)聽器實(shí)現(xiàn)是在綁定數(shù)據(jù)時(shí)創(chuàng)建的,而不是在事件觸發(fā)時(shí)創(chuàng)建的选浑。
要將事件分配給其處理程序,請(qǐng)使用常規(guī)綁定表達(dá)式隧膘,其值是要調(diào)用的方法名稱疹吃。 例如歉摧,如果你的數(shù)據(jù)對(duì)象有兩個(gè)方法:
public class MyHandlers {
public void onClickFriend(View view) { ... }
}
綁定表達(dá)式可以為 View
分配一個(gè)點(diǎn)擊監(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>
請(qǐng)注意,表達(dá)式中方法的簽名必須與監(jiān)聽器對(duì)象中方法的簽名完全匹配膝但。
監(jiān)聽器綁定
監(jiān)聽器綁定是事件發(fā)生時(shí)運(yùn)行的綁定表達(dá)式。類似于方法引用泳炉,但是允許你運(yùn)行任意的數(shù)據(jù)綁定表達(dá)式。 此功能適用于 Gradle 2.0 版及更高版本的 Android Gradle 插件刨肃。
在方法引用中,方法的參數(shù)必須與事件偵聽器的參數(shù)匹配盔然。 在監(jiān)聽器綁定中,只有你的返回值必須與監(jiān)聽器的期望返回值相匹配(除非它返回值為 void )站绪。 例如魂挂,您可以有一個(gè)具有以下方法的 Presenter
類:
public class Presenter {
public void onSaveClick(Task task){}
}
然后你可以綁定你的點(diǎn)擊事件到你的類中锰蓬,例如:
<?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āng)表達(dá)式中有回調(diào)時(shí),數(shù)據(jù)綁定會(huì)自動(dòng)為事件創(chuàng)建必要的偵聽器和注冊(cè)表要尔。 當(dāng)視圖觸發(fā)事件時(shí),數(shù)據(jù)綁定將評(píng)估給定的表達(dá)式还惠。 就像在常規(guī)的綁定表達(dá)式一樣蚕键,當(dāng)這些監(jiān)聽器表達(dá)式被評(píng)估的時(shí)候,你仍然可以獲取數(shù)據(jù)綁定的空值和保證線程安全誊爹。
請(qǐng)注意,在上面的例子中椎镣,我們沒有定義傳入 onClick(android.view.View)
的視圖參數(shù)冷守。 監(jiān)聽器綁定為監(jiān)聽器參數(shù)提供了兩個(gè)選擇:您可以忽略該方法的所有參數(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)}"
你可以在 lambda 表達(dá)式中使用多個(gè)參數(shù):
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)聽長(zhǎng)按事件,你的表達(dá)式應(yīng)該返回布爾值猜欺。
public class Presenter {
public boolean onLongClick(View view, Task task){}
}
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"
如果由于空對(duì)象而無(wú)法評(píng)估表達(dá)式,Data Binding 將返回該類型的默認(rèn) Java 值赋荆。 例如,引用類型為 null
嫉你,int
為 0
幽污,boolean
為false
等等簸搞。
如果您需要使用帶謂詞的表達(dá)式(例如三元),則可以使用 void
作為符號(hào)。
android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"
避免復(fù)雜的監(jiān)聽器
監(jiān)聽器表達(dá)式非常強(qiáng)大闰集,可以讓你的代碼變得非常容易閱讀。 另一方面蝠检,包含復(fù)雜表達(dá)式的監(jiān)聽器也會(huì)使您的布局難以閱讀和維護(hù)饲梭。這些表達(dá)式應(yīng)該像從 UI 中傳遞可用數(shù)據(jù)到回調(diào)方法一樣簡(jiǎn)單憔涉。你應(yīng)該從偵聽器表達(dá)式調(diào)用的回調(diào)方法內(nèi)實(shí)現(xiàn)業(yè)務(wù)邏輯。
存在一些專門的單擊事件處理程序国旷,它需要除 android:onClick
之外的其他屬性以避免沖突。 已創(chuàng)建了以下屬性以避免這種沖突:
Class | Listener Setter | Attribute |
---|---|---|
SearchView |
setOnSearchClickListener(View.OnClickListener) |
android:onSearchClick |
ZoomControls |
setOnZoomInClickListener(View.OnClickListener) |
android:onZoomIn |
ZoomControls |
setOnZoomOutClickListener(View.OnClickListener) |
android:onZoomOut |
布局文件細(xì)節(jié)
Imports
數(shù)據(jù)元素內(nèi)可以使用零個(gè)或多個(gè) import
元素吧雹。 這些就像在 Java 中一樣可以輕松地引用類到你的布局文件中雄卷。
<data>
<import type="android.view.View"/>
</data>
現(xiàn)在 View
類可以在你的綁定表達(dá)式中使用了。
<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
如果類名有沖突的話揣钦,其中一個(gè)類則需起別名了。
<import type="android.view.View"/>
<import type="com.example.real.estate.View" alias="Vista"/>
現(xiàn)在宇姚,在布局文件中浑劳,Vista
被當(dāng)作 com.example.real.estate.View
引入,View
被當(dāng)作 android.view.View
引入。 導(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òng)導(dǎo)入變量在你的的 IDE 中可能無(wú)法完成吏夯。 你的應(yīng)用程序仍然可以正常編譯,你可以通過在變量定義中使用完全限定的名稱來(lái)解決 IDE 的這個(gè)問題跺嗽。
<TextView
android:text="@{((User)(user.connection)).lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
當(dāng)在表達(dá)式中引用靜態(tài)字段和方法時(shí)植兰,也可以使用導(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.*
會(huì)被自動(dòng)導(dǎo)入筒繁。
Variables
data
元素內(nèi)可以使用任意的 variable
。 每個(gè)變量表示可以在布局中設(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>
變量類型在編譯時(shí)被檢查佳谦,所以如果一個(gè)變量實(shí)現(xiàn)了 Observable
或者一個(gè) observable collection
啥刻,那么它應(yīng)該被反映在類型中。 如果變量是沒有實(shí) Observable
接口的基類或接口映跟,那么它將不會(huì)被觀察!
當(dāng)不同的配置(例如橫向或縱向)有不同的布局文件時(shí)荸镊,變量將被合并张惹。 這些布局文件之間不得有沖突的變量定義。
生成的綁定類將為每個(gè)描述的變量設(shè)置一個(gè) setter
和 getter
方法拧额。 變量將采用默認(rèn)的 Java 值,直到調(diào)用 setter
為止 恭垦。對(duì)于引用類型為 null
,對(duì)于 int
為0
玄柏,對(duì)于 boolean
為 false
等绍坝。
自定義綁定類的名字
默認(rèn)情況下椎咧,根據(jù)布局文件的名稱生成一個(gè)綁定類,以大寫字母開頭脚牍,刪除下劃線(_)并之后的單詞首字母大寫,然后添加后綴 Binding
作谚。 這個(gè)類將被放置在模塊包下的數(shù)據(jù)綁定包中三娩。 例如,布局文件 contact_item.xml
將生成 ContactItemBinding
妹懒。 如果模塊包是 com.example.my.app
雀监,那么它將被放置在 com.example.my.app.databinding
中。
綁定類可以通過調(diào)整 data
元素的 class
屬性來(lái)重命名或放置在不同的包中眨唬。 例如:
<data class="ContactItem">
...
</data>
這會(huì)在模塊包中的數(shù)據(jù)綁定包中生成綁定類 ContactItem
会前。 如果該類應(yīng)該在模塊包中的其他包中生成,則可以用“.”作為前綴:
<data class=".ContactItem">
...
</data>
在這種情況下斋攀,直接在模塊包中生成了 ContactItem
闺魏。 如果提供完整的包,則可以使用任意的包:
<data class="com.example.ContactItem">
...
</data>
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
布局文件中都必須有一個(gè) 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>
表達(dá)式語(yǔ)言
共同特征
表達(dá)式語(yǔ)言看起來(lái)很像 Java 表達(dá)式。 這些是一樣的:
- Mathematical
+ - / * %
- String concatenation
+
- Logical &&
||
- Binary
& | ^
- Unary
+ - ! ~
- Shift
>> >>> <<
- Comparison
== > < >= <=
instanceof
- Grouping
()
- Literals - character, String, numeric,
null
- Cast
- Method calls
- Field access
- Array access
[]
- Ternary operator
?:
例如:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
缺少的操作
你在 Java 中使用的一些表達(dá)式語(yǔ)法并不支持綁定操作。
this
super
new
- 明確的泛型調(diào)用
空的合并運(yùn)算符
空合并運(yùn)算符 ??
會(huì)選擇左邊的運(yùn)算結(jié)果(如果它不是 null
的話)或右邊的運(yùn)算結(jié)果(如果它是 null
的話)。
android:text="@{user.displayName ?? user.lastName}"
這在功能上等同于:
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
屬性引用
當(dāng)一個(gè)表達(dá)式引用一個(gè)類的屬性時(shí)焊刹,它對(duì)字段错忱,setter
和 ObservableFields
使用相同的格式。
android:text="@{user.lastName}"
避免空指針異常
生成的數(shù)據(jù)綁定代碼會(huì)自動(dòng)檢查空值并避免空指針異常死遭。 例如睡蟋,在表達(dá)式 @ {user.name}
中傍菇,如果 user
為 null,則 user.name
將被分配其默認(rèn)值(null
)。 如果引用 user.age
,其中age是一個(gè) int
失息,那么它將默認(rèn)為0。
集合
通用的集合:數(shù)組,列表诗轻,SparseArray 养晋,map,可以使用 []
運(yùn)算符來(lái)方便地訪問钩述。
<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]}"
字符串文本
在屬性值兩邊使用單引號(hào)時(shí)颓屑,則表達(dá)式中使用雙引號(hào):
android:text='@{map["firstName"]}'
也可以使用雙引號(hào)來(lái)包圍屬性值。 這樣做時(shí),字符串文字應(yīng)該使用單引號(hào) '
或者反引號(hào)(`)指蚁。
android:text="@{map[`firstName`}"
android:text="@{map['firstName']}"
資源
使用正常的語(yǔ)法可以將資源作為表達(dá)式的一部分進(jìn)行訪問:
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
格式字符串和復(fù)數(shù)可以通過提供參數(shù)來(lái)評(píng)估:
android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"
當(dāng)一個(gè)復(fù)數(shù)有多個(gè)參數(shù)時(shí)傍衡,所有參數(shù)都應(yīng)該傳遞:
Have an orange
Have %d oranges
android:text="@{@plurals/orange(orangeCount, orangeCount)}"
一些資源需要明確的類型評(píng)估:
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 |
Data Objects
任何普通的舊 Java 對(duì)象(POJO)都可以用于數(shù)據(jù)綁定宏怔,但修改 POJO 不會(huì)導(dǎo)致 UI
更新。 數(shù)據(jù)綁定的真正威力在于通過給你的數(shù)據(jù)對(duì)象在數(shù)據(jù)改變時(shí)提供通知泼橘。 有三種不同的數(shù)據(jù)更改通知機(jī)制椎木,Observable objects
, observable fields
, observable collections
.
當(dāng)這些可觀察的數(shù)據(jù)對(duì)象被綁定到 UI鲤脏,并且數(shù)據(jù)對(duì)象的屬性改變時(shí)刨秆,UI 將被自動(dòng)更新。
Observable Objects
實(shí)現(xiàn) Observable
接口的類將允許綁定單個(gè)偵聽器附加到綁定對(duì)象姊氓,以偵聽該對(duì)象上所有屬性的更改瘦锹。
Observable
接口具有添加和刪除偵聽器的功能蝙斜,但通知是由開發(fā)者決定的猴贰。 為了簡(jiǎn)化開發(fā)交汤,創(chuàng)建了基類 BaseObservable
圈浇,以實(shí)現(xiàn)偵聽器注冊(cè)機(jī)制庶弃。 數(shù)據(jù)類實(shí)現(xiàn)者仍然負(fù)責(zé)通知屬性的更改麻裁。 這是通過給 getter 分配一個(gè) Bindable
注解并通知 setter 來(lái)完成的诈悍。
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 類中生成一個(gè)條目祸轮。 BR 類文件將在模塊包中生成。 如果數(shù)據(jù)類的基類沒有改變侥钳,Observable
接口可以使用方便的 PropertyChangeRegistry
來(lái)實(shí)現(xiàn)适袜,以有效地存儲(chǔ)和通知監(jiān)聽器。
ObservableFields
創(chuàng)建 Observable
類需要做一點(diǎn)工作舷夺,所以想要節(jié)省時(shí)間或擁有很少屬性的開發(fā)人員可以使用 ObservableField
及其同胞 ObservableBoolean
苦酱,ObservableByte
,ObservableChar
给猾,ObservableShort
疫萤,ObservableInt
,ObservableLong
耙册,ObservableFloat
给僵,ObservableDoubl
和 ObservableParcelable
。 ObservableFields
是具有單個(gè)字段的獨(dú)立的可觀察對(duì)象详拙。 原始版本在訪問操作期間避免裝箱和取消裝箱。 要使用蔓同,請(qǐng)?jiān)跀?shù)據(jù)類中創(chuàng)建一個(gè)公共 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();
}
就是這樣饶辙!要訪問該值,請(qǐng)使用 set 和 get 方法訪問:
user.firstName.set("Google");
int age = user.age.get();
Observable Collections
一些應(yīng)用程序使用更多的動(dòng)態(tài)結(jié)構(gòu)來(lái)保存數(shù)據(jù)斑粱,觀察集合允許對(duì)這些數(shù)據(jù)對(duì)象進(jìn)行鍵值訪問弃揽。當(dāng)鍵是引用類型(如 String)時(shí),ObservableArrayMap
非常有用则北。
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
在布局文件中矿微,map 通過字符串鍵來(lái)訪問:
<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)鍵是整形是,可以使用 ObservableArrayList
:
ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);
在布局中尚揣,列表可以通過索引來(lái)訪問:
<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"/>
生成綁定類
生成的綁定類將布局變量與布局中的視圖鏈接起來(lái)涌矢。 如前所述,綁定的名稱和包可能是自定義的快骗。 生成的綁定類都擴(kuò)展了 ViewDataBinding
娜庇。
創(chuàng)建
應(yīng)該在 inflate 之后立即創(chuàng)建綁定塔次,以確保 View 層次結(jié)構(gòu)不受干擾。 有幾種方法可以綁定到布局名秀。 最常見的是在綁定類中使用靜態(tài)方法励负。inflate 方法 inflate View 層次結(jié)構(gòu),一步到位匕得。 有一個(gè)更簡(jiǎn)單的版本继榆,只需要一個(gè) LayoutInflater
和一個(gè) ViewGroup
:
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);
如果布局使用不同的機(jī)制 inflate,它可能會(huì)被分開綁定:
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
有時(shí)綁定不能預(yù)先知道汁掠。 在這種情況下裕照,可以使用 DataBindingUtil
類創(chuàng)建綁定:
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId, parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);
Views With IDs
將在布局中為每個(gè)視圖生成一個(gè)公開的 final 字段。 該綁定在視圖層次結(jié)構(gòu)上執(zhí)行單個(gè)傳遞调塌,提取帶有 ID 的視圖晋南。 這個(gè)機(jī)制可以比調(diào)用多個(gè)視圖的 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>
會(huì)生成帶有一下字段的綁定類:
public final TextView firstName;
public final TextView lastName;
IDs 不像沒有數(shù)據(jù)綁定那樣必要羔砾,但是仍然有一些情況下代碼需要訪問視圖负间。
變量
每個(gè)變量將被賦予訪問器方法。
<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>
會(huì)在綁定類中生成 setter 和 getter 方法:
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);
ViewStubs
ViewStub
與普通視圖有點(diǎn)不同姜凄。 他們從不可見的時(shí)候開始政溃,當(dāng)他們要么變得可見時(shí),要么被明確告知 inflate 時(shí)态秧,他們通過 inflate 另一個(gè)布局來(lái)取代布局董虱。
由于 ViewStub
本質(zhì)上從視圖層次中消失,所以綁定對(duì)象中的視圖也必須消失以允許收集申鱼。 因?yàn)橐晥D是 final 的愤诱,所以 ViewStubProxy
對(duì)象代替了ViewStub
,當(dāng) ViewStub
存在時(shí)捐友,開發(fā)人員可以訪問 ViewStub
淫半,并且在 ViewStub
被 inflate 時(shí)也可以訪問被 inflate 的視圖。
當(dāng) inflate 另一個(gè)布局時(shí)匣砖,必須為新的布局建立綁定科吭。因此,ViewStubProxy
必須偵聽 ViewStub
的 ViewStub.OnInflateListener
并在此時(shí)建立綁定猴鲫。由于只有一個(gè)可以存在对人,ViewStubProxy
允許開發(fā)者在建立綁定之后設(shè)置一個(gè) OnInflateListener
對(duì)象。
高級(jí)綁定
動(dòng)態(tài)變量
有時(shí)拂共,特定的綁定類將不被知道牺弄。 例如,針對(duì)任意布局的 RecyclerView.Adapter
將不知道具體的綁定類匣缘。 它仍然必須在 onBindViewHolder(VH,int)
期間分配綁定值猖闪。
在這個(gè)例子中鲜棠,RecyclerView
綁定的所有布局都有一個(gè) item 變量。BindingHolder
有一個(gè)返回 ViewDataBinding
基類的 getBinding
方法培慌。
public void onBindViewHolder(BindingHolder holder, int position) {
final T item = mItems.get(position);
holder.getBinding().setVariable(BR.item, item);
holder.getBinding().executePendingBindings();
}
立即綁定
當(dāng)變量或 observable 變化時(shí)豁陆,綁定將被安排在下一幀之前改變。但有時(shí)候吵护,綁定必須立即執(zhí)行盒音。要強(qiáng)制執(zhí)行,請(qǐng)使用 executePendingBindings()
方法馅而。
后臺(tái)線程
只要不是集合祥诽,就可以在后臺(tái)線程中更改數(shù)據(jù)模型。數(shù)據(jù)綁定將在評(píng)估時(shí)本地化每個(gè)變量/字段瓮恭,以避免任何并發(fā)問題雄坪。
屬性設(shè)置
每當(dāng)綁定值發(fā)生變化時(shí),生成的綁定類必須使用綁定表達(dá)式在視圖上調(diào)用setter方法屯蹦。 數(shù)據(jù)綁定框架可以自定義調(diào)用哪個(gè)方法來(lái)設(shè)置值维哈。
自動(dòng)的設(shè)置器
對(duì)于一個(gè)屬性,數(shù)據(jù)綁定將試圖找到設(shè)置屬性的方法登澜。屬性的命名空間并不重要阔挠,只有屬性名稱本身才重要。例如脑蠕,與 TextView
的屬性 android:text
相關(guān)聯(lián)的表達(dá)式將查找 setText(String)购撼。 如果表達(dá)式返回 int,那么數(shù)據(jù)綁定將搜索一個(gè) setText(int) 方法谴仙。請(qǐng)注意讓表達(dá)式返回正確的類型迂求,如果需要的話就進(jìn)行轉(zhuǎn)換。即使給定名稱不存在任何屬性狞甚,數(shù)據(jù)綁定也可以工作锁摔。 然后,您可以使用數(shù)據(jù)綁定輕松地為任何 setter 創(chuàng)建屬性哼审。 例如,support 庫(kù)中的 DrawerLayout
沒有任何屬性孕豹,但是有很多 setter涩盾。 您可以使用自動(dòng)設(shè)置器來(lái)使用其中的一個(gè)。
<android.support.v4.widget.DrawerLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:scrimColor="@{@color/scrim}"
app:drawerListener="@{fragment.drawerListener}"/>
重命名設(shè)置器
一些屬性的設(shè)置器會(huì)與名稱不匹配励背。 對(duì)于這些方法春霍,一個(gè)屬性可能通過 BindingMethods
注解與設(shè)置器關(guān)聯(lián)。 這必須與一個(gè)類相關(guān)聯(lián)叶眉,每個(gè)重命名的方法一個(gè)包含一個(gè) BindingMethod
注解址儒。例如芹枷,android:tint
屬性確實(shí)與 setImageTintList(ColorStateList)
關(guān)聯(lián),而不是 setTint
莲趣。
@BindingMethods({
@BindingMethod(type = "android.widget.ImageView",
attribute = "android:tint",
method = "setImageTintList"),
})
開發(fā)人員不太可能需要重命名設(shè)置器鸳慈, 安卓框架已經(jīng)為這些屬性實(shí)現(xiàn)了。
自定義設(shè)置器
一些屬性需要自定義綁定邏輯喧伞。 例如走芋,android:paddingLeft
屬性沒有關(guān)聯(lián)的設(shè)置器。 相反潘鲫,setPadding(eft, top, right, bottom) 存在翁逞。 使用 BindingAdapter
注釋的靜態(tài)綁定適配器方法允許開發(fā)人員自定義如何調(diào)用屬性的設(shè)置器。
安卓屬性已經(jīng)創(chuàng)建了 BindingAdapters
溉仑。 例如挖函,這里是 paddingLeft
:
@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
綁定適配器對(duì)其他類型的自定義非常有用。 例如浊竟,一個(gè)自定義的加載器可以被調(diào)用脫機(jī)線程來(lái)加載一個(gè)圖像怨喘。當(dāng)發(fā)生沖突時(shí),開發(fā)人員創(chuàng)建的綁定適配器將覆蓋數(shù)據(jù)綁定默認(rèn)適配器逐沙。您也可以讓適配器接收多個(gè)參數(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());
}
}
事件處理器只能用于只有一個(gè)抽象方法的接口或抽象類胧后。例如:
@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)一個(gè)監(jiān)聽器有多個(gè)方法時(shí),它必須被分成多個(gè)監(jiān)聽器抱环。例如壳快,View.OnAttachStateChangeListener
有兩個(gè)方法:onViewAttachedToWindow()
和 onViewDetachedFromWindow()
。然后我們必須創(chuàng)建兩個(gè)接口來(lái)區(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);
}
因?yàn)楦囊粋€(gè)偵聽器也會(huì)影響另一個(gè)偵聽器眶痰,所以我們必須有三個(gè)不同的綁定適配器,一個(gè)用于每個(gè)屬性梯啤,另一個(gè)用于兩個(gè)竖伯,它們都應(yīng)該被設(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ù)雜一點(diǎn),因?yàn)橐晥D對(duì)偵聽器使添加和刪除七婴,而不是對(duì) View.OnAttachStateChangeListener
使用set方法祟偷。 android.databinding.adapters.ListenerUtil
類有助于跟蹤以前的監(jiān)聽器,以便它們可以在綁定適配器中被移除打厘。通過使用 @TargetApi(VERSION_CODES.HONEYCOMB_MR1)
注解接口 OnViewDetachedFromWindow
和OnViewAttachedToWindow
修肠,數(shù)據(jù)綁定代碼生成器知道只應(yīng)在 API 12 或以上的設(shè)備上調(diào)用 addOnAttachStateChangeListener(View.OnAttachStateChangeListener)
來(lái)運(yùn)行運(yùn)行偵聽器。
轉(zhuǎn)換器
對(duì)象轉(zhuǎn)換
從綁定表達(dá)式返回一個(gè)對(duì)象時(shí)婚惫,將從自動(dòng)氛赐,重命名和自定義的設(shè)置器中選擇一個(gè)設(shè)置器。 該對(duì)象將被轉(zhuǎn)換為所選設(shè)置器的參數(shù)類型先舷。
這對(duì)于那些使用 ObservableMaps 來(lái)保存數(shù)據(jù)的開發(fā)者來(lái)說是很方便的艰管。例如:
<TextView
android:text='@{userMap["lastName"]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
userMap
返回一個(gè)對(duì)象,該對(duì)象將被自動(dòng)轉(zhuǎn)換為在 setText(CharSequence)
中找到的參數(shù)類型蒋川。 當(dāng)參數(shù)類型可能混淆時(shí)牲芋,開發(fā)者需要在表達(dá)式中輸入。
自定義轉(zhuǎn)換
有時(shí)轉(zhuǎn)換應(yīng)該在特定類型之間自動(dòng)進(jìn)行捺球。 例如缸浦,設(shè)置 background 時(shí):
<View
android:background="@{isError ? @color/red : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
在這里,背景需要一個(gè) Drawable
氮兵,但是顏色是一個(gè)整數(shù)裂逐。每當(dāng)一個(gè) Drawable
被判斷該返回一個(gè)整數(shù)時(shí),該整形應(yīng)該被轉(zhuǎn)換成一個(gè) ColorDrawable
泣栈。 這個(gè)轉(zhuǎn)換是通過一個(gè)帶有 BindingConversion
注解的靜態(tài)方法完成的:
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}
請(qǐng)注意卜高,轉(zhuǎn)換只發(fā)生在設(shè)置器級(jí)別,所以不允許混合類型南片,如下所示:
<View
android:background="@{isError ? @drawable/error : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
Android Studio 支持?jǐn)?shù)據(jù)綁定
Android Studio 支持?jǐn)?shù)據(jù)綁定代碼的許多代碼編輯功能掺涛。例如,它支持?jǐn)?shù)據(jù)綁定表達(dá)式的以下功能:
- 語(yǔ)法高亮
- 表達(dá)式語(yǔ)法錯(cuò)誤的提示
- XML代碼完成
- 包括導(dǎo)航(如導(dǎo)航到聲明)和快速文檔的參考
注意:如果沒有錯(cuò)誤疼进,則數(shù)組和一般類型(如
Observable
類)可能會(huì)顯示錯(cuò)誤薪缆。
預(yù)覽窗格顯示數(shù)據(jù)綁定表達(dá)式的默認(rèn)值(如果提供的話)。在以下示例摘錄布局XML文件中的元素時(shí)伞广,預(yù)覽窗格將在 TextView
中顯示 PLACEHOLDER
默認(rèn)文本值拣帽。
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName, default=PLACEHOLDER}"/>
如果需要在項(xiàng)目設(shè)計(jì)階段顯示默認(rèn)值,則還可以使用工具屬性而不是默認(rèn)表達(dá)式值嚼锄,如 Design Time Layout Attributes 中所述诞外。
原文地址:https://developer.android.google.cn/topic/libraries/data-binding/index.html