轉(zhuǎn)載
Android Data Binding(數(shù)據(jù)綁定)用戶指南
[TOC]
1)介紹
本文檔解釋了如何使用數(shù)據(jù)綁定庫來編寫聲明式布局,并且用最少的代碼來綁定你的app邏輯和layouts文件亲雪。
Data Binding庫既具有靈活性勇凭,又具有廣泛的兼容性——它是一個support持庫,因此你可以在所有的Android平臺最低能到Android 2.1(API等級7+)上使用它义辕。
為了使用數(shù)據(jù)綁定虾标,需要Android插件1.5.0 - alpha1或更高版本。
2)構(gòu)建環(huán)境
要開始使用Data Binding灌砖,首先需要在Android SDK Manager的支持庫里下載該庫璧函。
你的app要使用Data Binding,需要添加Data Binding到gradle構(gòu)建文件里基显,如下:
android {
....
dataBinding {
enabled = true
}
}
Data Binding插件將會在你的項目內(nèi)添加必需提供的以及編譯配置依賴蘸吓。
請確保您使用的是Android Studio的兼容版本。Android Studio的Data Binding插件需要Android Studio 1.3.0 或 更高版本撩幽。
3) Data Binding Layout文件
a) Data Binding表達(dá)式
Data Binding layout文件有點(diǎn)不同的是:起始根標(biāo)簽是layout库继,接下來一個data元素以及一個view的根元素。這個view元素就是你沒有使用Data Binding的layout文件的根元素窜醉。舉例說明如下:
<?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內(nèi)描述了一個名為user的變量屬性宪萄,使其可以在這個layout中使用:
<variable name="user" type="com.example.User"/>
在layout的屬性表達(dá)式寫作@{},下面是一個TextView的text設(shè)置為user的firstName屬性:
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
b)Data對象
假設(shè)你有一個user的plain-old Java Object(POJO):
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ù)榨惰。在app中它是常見的拜英,可以讀取一次并且之后從不改變。當(dāng)然也可以使用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;
}
}
從Data Binding的角度來看琅催,這兩個類是等價的居凶。用于TextView中的android:text
屬性的表達(dá)式@{user.firstName}
將訪問前者POJO對象中的firstName和后者JavaBeans對象中的getFirstName()
方法虫给。
c)Binding數(shù)據(jù)
默認(rèn)情況下,一個Binding類會基于layout文件的名稱而產(chǎn)生排监,將其轉(zhuǎn)換為Pascal case(譯注:首字母大寫的命名規(guī)范)并且添加“Binding”后綴狰右。上述的layout文件是main_activity.xml
,因此生成的類名是MainActivityBinding
舆床。此類包含從layout屬性到layout的Views中所有的bindings(例如user
變量),并且它還知道如何給Binding表達(dá)式分配數(shù)值嫁佳。創(chuàng)建bindings的最簡單的方式是在inflating(譯注:layout文件與Activity/Fragment的“鏈接”)期間如下:
@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)行app后,你將會看到Test User蒿往∈⒖眩或者你可以通過如下獲取View:
MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());
如果你在ListView或者RecyclerView adapter使用Data Binding時,你可能會使用:
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
d)事件處理
數(shù)據(jù)綁定允許你編寫表達(dá)式來處理view分派的事件瓤漏。事件屬性名字取決于監(jiān)聽器方法名字腾夯。例如View.OnLongClickListener有onLongClick()的方法,因此這個事件的屬性是android:onLongClick
蔬充。處理事件有兩種方法:
方法引用:在表達(dá)式中蝶俱,可以引用符合偵聽器方法簽名的方法。當(dāng)表達(dá)式計算到方法引用時饥漫,數(shù)據(jù)綁定將方法引用和所有者對象包裝在偵聽器中榨呆,并將該偵聽器設(shè)置為目標(biāo)視圖。如果表達(dá)式計算為null庸队,那么數(shù)據(jù)綁定不會創(chuàng)建一個監(jiān)聽器积蜻,而是設(shè)置一個空監(jiān)聽器。
偵聽器綁定:這些是在事件發(fā)生時評估的lambda表達(dá)式彻消。數(shù)據(jù)綁定總是創(chuàng)建一個監(jiān)聽器竿拆,它在視圖上設(shè)置。當(dāng)事件被發(fā)送時宾尚,監(jiān)聽器將計算lambda表達(dá)式
方法引用
事件可以直接綁定到處理程序方法丙笋,類似于android:onClick可以被分配到活動中的方法。與視圖# onClick屬性相比央勒,一個主要優(yōu)點(diǎn)是在編譯時處理表達(dá)式不见,因此如果該方法不存在或它的簽名不正確,則會收到一個編譯時錯誤崔步。
方法引用和偵聽器綁定之間的主要區(qū)別在于稳吮,實際的偵聽器實現(xiàn)是在數(shù)據(jù)綁定時創(chuàng)建的,而不是在事件觸發(fā)時創(chuàng)建井濒。如果您喜歡在事件發(fā)生時評估表達(dá)式灶似,則應(yīng)該使用偵聽器綁定列林。
要將事件分配給它的處理程序,使用一個普通的綁定表達(dá)式酪惭,其中值是要調(diào)用的方法名稱希痴。例如,如果您的數(shù)據(jù)對象有兩種方法:
public class MyHandlers {
public void onClickFriend(View view) { ... }
}
綁定表達(dá)式可以為視圖指定單擊偵聽器:
<?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>
偵聽器綁定
偵聽器綁定是在事件發(fā)生時運(yùn)行的綁定表達(dá)式春感。它們類似于方法引用砌创,但它們允許您運(yùn)行任意數(shù)據(jù)綁定表達(dá)式。這個功能可以使用Android Gradle插件來升級版本2.0和更高版本鲫懒。
在方法引用中嫩实,方法的參數(shù)必須與事件監(jiān)聽器的參數(shù)相匹配。在偵聽器綁定中窥岩,只有您的返回值必須匹配偵聽器的預(yù)期返回值(除非它期望void)甲献。例如,您可以有一個具有以下方法的presenter類
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>
偵聽器由lambda表達(dá)式表示晃洒,這些表達(dá)式只能作為表達(dá)式的根元素。當(dāng)在表達(dá)式中使用回調(diào)時朦乏,數(shù)據(jù)綁定會自動為事件創(chuàng)建必要的偵聽器和寄存器球及。當(dāng)視圖觸發(fā)事件時,數(shù)據(jù)綁定將計算給定的表達(dá)式集歇。與常規(guī)綁定表達(dá)式一樣桶略,在對這些偵聽器表達(dá)式進(jìn)行評估時,仍然可以獲得數(shù)據(jù)綁定的null和線程安全性诲宇。
請注意际歼,在上面的示例中,我們還沒有定義傳入onClick的視圖參數(shù)(android.view.View)姑蓝。偵聽器綁定為偵聽器參數(shù)提供了兩個選擇:您可以忽略所有參數(shù)鹅心,也可以對所有參數(shù)進(jìn)行命名。如果您喜歡命名參數(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)}" />
如果您正在監(jiān)聽的事件返回一個類型不是void的值输枯,那么您的表達(dá)式必須返回相同類型的值。例如占贫,如果您想要偵聽長時間單擊事件桃熄,您的表達(dá)式應(yīng)該返回布爾值。
public class Presenter {
public boolean onLongClick(View view, Task task){}
}
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"
如果由于null對象不能計算表達(dá)式型奥,那么數(shù)據(jù)綁定將返回該類型的默認(rèn)Java值瞳收。例如碉京,引用類型為null,int為0,布爾值為false等等螟深。
如果需要使用謂詞的表達(dá)式(如ternary)谐宙,則可以將void用作符號。
android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"
避免復(fù)雜的監(jiān)聽
偵聽器表達(dá)式非常強(qiáng)大界弧,可以使您的代碼非常容易閱讀凡蜻。另一方面,包含復(fù)雜表達(dá)式的偵聽器使您的布局難以閱讀和無法維護(hù)垢箕。這些表達(dá)式應(yīng)該像將可用的數(shù)據(jù)從UI傳遞到回調(diào)方法一樣簡單咽瓷。您應(yīng)該在從偵聽器表達(dá)式調(diào)用的回調(diào)方法中實現(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 |
4)深入Layout文件
a)Import
零個或多個import元素可能在data元素中使用月匣。這些只用在你的layout文件中添加引用,就像在Java中:
<data>
<import type="android.view.View"/>
</data>
現(xiàn)在奋姿,View可以使用你的Binding表達(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"/>
這樣,在該layout文件中Vista
對應(yīng)com.example.real.estate.View
称诗,而View
對應(yīng)android.view.View
萍悴。導(dǎo)入的類型可以在Variable和表達(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還沒有處理imports,所以自動導(dǎo)入Variable在你的IDE不能使用寓免。您的app仍會正常編譯癣诱,你可以在您的Variable定義中使用完全符合規(guī)定的名稱來解決該IDE問題。
<TextView
android:text="@{((User)(user.connection)).lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
導(dǎo)入的類型還可以在表達(dá)式中使用static屬性和方法:
<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)入的撕予。
b)Variables
在data
中可以使用任意數(shù)量的variable
元素。每一個variable
元素描述了一個用于layout文件中Binding表達(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>
該Variable
類型在編譯時檢查实抡,因此如果一個Variable實現(xiàn)了Observable
或observable collection
,這應(yīng)該反映在類型中欢策。(譯注:需要查找資料來理解)如果variable是一個沒有實現(xiàn)Observable接口的基本類或者接口吆寨,Variables不會被observed!
當(dāng)對于多種配置有不同的layout文件時(如踩寇,橫向或縱向)啄清,Variables會被合并。這些layout文件之間必須不能有沖突的Variable定義姑荷。
產(chǎn)生的Binding類對于每一個描述的Variables都會有setter和getter盒延。這些Variables會使用默認(rèn)的Java值 - null(引用類型)缩擂、0(int)、false(boolean)等等添寺,直到調(diào)用setter時胯盯。
c)自定義Binding類名稱
默認(rèn)情況下,Binding類的命名是基于所述layout文件的名稱计露,用大寫開頭博脑,除去下劃線()以及()后的第一個字母大寫,然后添加“Binding”后綴票罐。這個類將被放置在一個模塊封裝包里的databinding
封裝包下叉趣。例如,所述layout文件contact_item.xml
將生成ContactItemBinding
该押。如果模塊包是com.example.my.app
疗杉,那么它將被放置在com.example.my.app.databinding
。
Binding類可通過調(diào)整data元素中的class屬性來重命名或放置在不同的包中蚕礼。例如:
<data class="ContactItem">
...
</data>
在模塊封裝包的databinding包中會生成名為ContactItem
的Binding類烟具。如果要想讓該類生成在不同的包種,你需要添加前綴.奠蹬,如下:
<data class=".ContactItem">
...
</data>
在這個情況下朝聋,ContactItem類直接在模塊包種生成《谠辏或者你可以提供整個包名:
<data class="com.example.ContactItem">
...
</data>
d)Includes
通過使用application namespace以及在屬性中的Variable名字從容器layout中傳遞Variables到一個被包含的layout:
<?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
兩個layout文件中必需要有user
variable
Data binding不支持包括作為合并元素的直接子元素冀痕。例如,不支持以下布局:
<?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>
e)表達(dá)式
-
常用表達(dá)式跟Java表達(dá)式很像狸演,以下這些是一樣的:
[ ] 數(shù)學(xué) + - / * %
[ ] 字符串連接 +
[ ] 邏輯 && ||
[ ] 二進(jìn)制 & | ^
[ ] 一元運(yùn)算 + - ! ~
[ ] 移位 >> >>> <<
[ ] 比較 == > < >= <=
[ ] instanceof
[ ] 分組 ()
[ ] null
[ ] Cast
[ ] 方法調(diào)用
[ ] 數(shù)據(jù)訪問 []
[ ] 三元運(yùn)算 ?:
示例:
android:text="@{String.valueOf(index + 1)}" android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}" android:transitionName='@{"image_" + id}'
-
缺少的操作:
在Java中可以使用的表達(dá)式語法中缺少一些操作言蛇。
- [ ] this
- [ ] super
- [ ] new
- [ ] 顯式泛型調(diào)用
-
Null合并操作
??
- 左邊的對象如果它不是null,選擇左邊的對象严沥;或者如果它是null猜极,選擇右邊的對象:android:text="@{user.displayName ?? user.lastName}"
上的寫法相當(dāng)于下面的寫法:
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
-
屬性引用
第一個已經(jīng)在前邊提到了
a)Data Binding
表達(dá)式:JavaBean引用的簡短格式。
當(dāng)一個表達(dá)式引用一個類的屬性消玄,它仍使用同樣的格式對于字段跟伏、getters以及ObservableFields。android:text="@{user.lastName}"
-
避免 NullPointerException
Data Binding代碼生成時自動檢查是否為nulls來避免出現(xiàn)null pointer exceptions錯誤翩瓜。例如受扳,在表達(dá)
式@{user.name}
中,如果user
是null兔跌,user.name
會賦予它的默認(rèn)值(null)勘高。如果你引用user.age
,age是int
類型,那么它的默認(rèn)值是0华望。 -
集合
常用的集合:arrays蕊蝗、lists、sparse lists以及maps赖舟,為了簡便都可以使用
[]
來訪問蓬戚。<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āng)使用單引號包含屬性值時,在表達(dá)式中使用雙引號很容易:
android:text='@{map["firstName"]}'
使用雙引號來包含屬性值也是可以的宾抓。字符串前后需要使用"`":
android:text="@{map[`firstName`}" android:text="@{map['firstName']}"
-
Resources
使用正常的表達(dá)式來訪問resources也是可行的:
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ù)都會通過:
Have an orange Have %d oranges android:text="@{@plurals/orange(orangeCount, orangeCount)}"
類型 正常引用 表達(dá)式引用 String[] @array @stringArray int[] @array @intArray TypedArray @array @typedArray Animator @animator @animator StateListAnimator @animator @stateListAnimator color int @color @color ColorStateList @color @colorStateList 一些資源需要顯式類型判斷:
類型 正常引用 表達(dá)式引用 String[] @array @stringArray int[] @array @intArray TypedArray @array @typedArray Animator @animator @animator StateListAnimator @animator @stateListAnimator color int @color @color ColorStateList @color @colorStateList
5)Data 對象
任何Plain old Java object(POJO)可用于Data Binding,但修改POJO不會導(dǎo)致UI更新石洗。Data Binding的真正能力是當(dāng)數(shù)據(jù)變化時幢泼,可以通知給你的Data對象。有三種不同的數(shù)據(jù)變化通知機(jī)制:Observable
對象讲衫、ObservableFields
以及observable collections
缕棵。
當(dāng)這些可觀察Data對象綁定到UI,Data對象屬性的更改后涉兽,UI也將自動更新挥吵。
a)Observable 對象
實現(xiàn)android.databinding.Observable
接口的類可以允許附加一個監(jiān)聽器到Bound對象以便監(jiān)聽對象上的所有屬性的變化红符。
Observable
接口有一個機(jī)制來添加和刪除監(jiān)聽器扁瓢,但通知與否由開發(fā)人員管理呵恢。為了使開發(fā)更容易,一個BaseObservable
的基類為實現(xiàn)監(jiān)聽器注冊機(jī)制而創(chuàng)建矿辽。Data實現(xiàn)類依然負(fù)責(zé)通知當(dāng)屬性改變時。這是通過指定一個Bindable
注解給getter以及setter內(nè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類文件中生成一個Entry。BR類文件會在模塊包內(nèi)生成折柠。如果用于Data類的基類不能改變宾娜,Observable
接口通過方便的PropertyChangeRegistry
來實現(xiàn)用于儲存和有效地通知監(jiān)聽器。
b)Observable 字段
一些小工作會涉及到創(chuàng)建Observable類扇售,因此那些想要節(jié)省時間或者幾乎沒有幾個屬性的開發(fā)者可以使用·ObservableFields·前塔。·ObservableFields·是自包含具有單個字段的observable對象承冰。它有所有基本類型和一個是引用類型华弓。要使用它需要在data對象中創(chuàng)建public 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();
c)Observable 集合
一些app使用更多的動態(tài)結(jié)構(gòu)來保存數(shù)據(jù)寂屏。Observable集合允許鍵控訪問這些data對象。ObservableArrayMap
用于鍵是引用類型,如String
。
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
在layout文件中迁霎,通過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"/>
ObservableArrayList用于鍵是整數(shù):
ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);
在layout文件中吱抚,通過索引可以訪問list:
<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)Binding生成
Binding類的生成鏈接了layout中variables與Views。如前面所討論的考廉,Binding的名稱和包名可以定制秘豹。所生成的Binding類都擴(kuò)展了android.databinding.ViewDataBinding
。
a)創(chuàng)建
Binding應(yīng)在inflation之后就立馬創(chuàng)建芝此,以確保View層次結(jié)構(gòu)不在之前打擾layout中的binding到views上的表達(dá)式憋肖。有幾個方法可以綁定到一個layout。最常見的是在Binding類上使用靜態(tài)方法.inflate
方法載入View的層次結(jié)構(gòu)并且綁定到它只需這一步婚苹。還有一個更簡單的版本岸更,只需要LayoutInflater
還有一個是采用ViewGroup
:
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);
如果使用不同的機(jī)制載入layout,他可一分開綁定:
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
有時Binding不能提前知道膊升,對于這種情況怎炊,可以使用DataBindingUtil類來創(chuàng)建Binding:
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);
b)帶ID的Views
在layout中對于每個帶ID的View會生成一個public final字段。Binding在View層次結(jié)構(gòu)上做單一的傳遞廓译,提取帶ID的Views评肆。這種機(jī)制比起某些Views使用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>
它會生成如下的Binding類:
public final TextView firstName;
public final TextView lastName;
IDs不像沒有Data Bindings那樣幾乎沒有必要非区,但是仍然會有一些實例需要從代碼中訪問Views瓜挽。
c)Variables
每個Variable會有訪問方法。
<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>
它會在Binding中生成setters和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);
d)ViewStubs
ViewStubs跟正常的Views略有不同征绸。他們開始時是不可見的久橙,當(dāng)他們要么設(shè)置為可見或被明確告知要載入時,它們通過載入另外一個layout取代了自己管怠。
由于ViewStub基本上從View的層次結(jié)構(gòu)上消失淆衷,在Binding對象的View也必須消失來允許被收集。因為Views是最后的渤弛,一個ViewStubProxy
對象取帶ViewStub祝拯,給開發(fā)者獲得了ViewStub,當(dāng)它存在以及還可以訪問載入的View層次結(jié)構(gòu)時當(dāng)ViewStub已被載入時她肯。
當(dāng)載入另一個layout佳头,為新的布局必需創(chuàng)建一個Binding。因此晴氨,ViewStubProxy
必需監(jiān)聽ViewStub
的OnInflateListener
監(jiān)聽器并在那個時候建立Binding畜晰。因為只有一個可以存在,ViewStubProxy
允許開發(fā)者在其上設(shè)置一個OnInflateListener
它會在建立Binding后調(diào)用瑞筐。
e)Binding進(jìn)階
-
動態(tài)Variables
有時凄鼻,不知道具體的Binding類腊瑟,例如,一個
RecyclerView
適配器對layouts任意操作并不知道具體的Binding類块蚌。它仍然必需在onBindViewHolder
期間賦值給Binding闰非。在這個例子中,該
RecyclerView
綁定的所有l(wèi)ayouts有一個“item”的Variable峭范。該BindingHolder
有一個getBinding
方法返回ViewDataBinding
财松。public void onBindViewHolder(BindingHolder holder, int position) { final T item = mItems.get(position); holder.getBinding().setVariable(BR.item, item); holder.getBinding().executePendingBindings(); }
-
直接Binding
當(dāng)一個variable或observable變化時,binding會在下一幀之前被計劃要改變纱控。有很多次辆毡,但是在Binding時必須立即執(zhí)行。要強(qiáng)制執(zhí)行甜害,使用
executePendingBindings()
方法舶掖。 -
后臺線程
只要它不是一個集合,你可以在后臺線程中改變你的數(shù)據(jù)模型尔店。在判斷是否要避免任何并發(fā)問題時眨攘,Data Binding會對每個Varialbe/field本地化。
7)屬性Setters
每當(dāng)綁定值的變化嚣州,生成的Binding類必須調(diào)用setter方法??鲫售。Data Binding框架有可以自定義賦值的方法。
a)自動Setters
對于一個屬性该肴,Data Binding試圖找到setAttribute
方法情竹。與該屬性的namespace并不什么關(guān)系,僅僅與屬性本身名稱有關(guān)匀哄。
例如鲤妥,有關(guān)TextView的android:text
屬性的表達(dá)式會尋找一個setText(String)
的方法。如果表達(dá)式返回一個int
拱雏,Data Binding會搜索的setText(int)
方法。注意:要表達(dá)式返回正確的類型底扳,如果需要的話使用casting
铸抑。Data Binding仍會工作即使沒有給定名稱的屬性存在。然后衷模,您可以通過Data Binding輕松地為任何setter“創(chuàng)造”屬性鹊汛。例如,DrawerLayout
沒有任何屬性阱冶,但大量的setters刁憋。您可以使用自動setters來使用其中的一個。
<android.support.v4.widget.DrawerLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:scrimColor="@{@color/scrim}"
app:drawerListener="@{fragment.drawerListener}"/>
b)重命名的Setters
一些有setters的屬性按名稱并不匹配木蹬。對于這些方法至耻,屬性可以通過BindingMethods注解相關(guān)聯(lián)。這必須與一個包含BindingMethod
注解的類相關(guān)聯(lián),每一個用于一個重命名的方法尘颓。例如走触,android:tint
屬性與setImageTintList
相關(guān)聯(lián),而不與setTint
相關(guān)疤苹。
@BindingMethods({
@BindingMethod(type = "android.widget.ImageView",
attribute = "android:tint",
method = "setImageTintList"),
})
以上例子互广,開發(fā)者需要重命名setters是不太可能了,android架構(gòu)屬性已經(jīng)實現(xiàn)了卧土。
c)自定義Setters
有些屬性需要自定義綁定邏輯惫皱。例如,對于android:paddingLeft
屬性并沒有相關(guān)setter尤莺。相反旅敷,setPadding(left, top, right, bottom)
是存在在。一個帶有BindingAdapter
注解的靜態(tài)綁定適配器方法允許開發(fā)者自定義setter如何對于一個屬性的調(diào)用缝裁。
Android的屬性已經(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());
}
Binding適配器對其他定制類型非常有用捷绑。例如韩脑,自定義loader可以用來異步載入圖像。
當(dāng)有沖突時粹污,開發(fā)人員創(chuàng)建的Binding適配器將覆蓋Data Binding默認(rèn)適配器段多。
您也可以創(chuàng)建可以接收多個參數(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}"/>
如果對于一個ImageViewimageUrl
和error都被使用并且imageUrl
是一個string類型以及error是一個drawable時壮吩,該適配器會被調(diào)用进苍。
- 匹配的過程中自定義namespaces將被忽略。
- 你也可以為Android namespaces寫適配器鸭叙。
綁定適配器方法可以在處理程序中選擇舊的值觉啊。一個取舊值和新值的方法應(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);
}
因為改變一個偵聽器也會影響另一個偵聽器学歧,因此我們必須有三個不同的綁定適配器罩引,一個用于每個屬性,一個用于兩者枝笨,它們都應(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),因為視圖使用添加和刪除偵聽器View.OnAttachStateChangeListener
相反的一組方法揭蜒。android.databinding.adapters.ListenerUtil
類幫助跟蹤以前的偵聽器,以便在綁定Adaper中刪除它們昭躺。
通過注解的接口OnViewDetachedFromWindow
和OnViewAttachedToWindow
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
,數(shù)據(jù)綁定代碼生成器知道聽眾只能運(yùn)行在蜂窩MR1時生成和新設(shè)備,支持的相同版本addOnAttachStateChangeListener(View.OnAttachStateChangeListener)
忌锯。
8)轉(zhuǎn)換
a)對象轉(zhuǎn)換
當(dāng)從Binding表達(dá)式返回一個對象,一個setter會從自動领炫、重命名以及自定義的setters中選擇偶垮。該對象將被轉(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)有關(guān)參數(shù)類型可能混亂時,開發(fā)人員需要在表達(dá)式中轉(zhuǎn)換葱峡。
b)自定義轉(zhuǎn)換
有時候轉(zhuǎn)換應(yīng)該是自動的在特定類型之間砚哗。例如,設(shè)置背景的時候:
<View
android:background="@{isError ? @color/red : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
這里砰奕,背景需要Drawable
對象蛛芥,但顏色是一個整數(shù)。不管何時有Drawable
并且返回值是一個整數(shù)军援,那么整數(shù)類型會被轉(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支持
Android Studio為數(shù)據(jù)綁定支持許多的代碼編輯胸哥。例如涯竟,它支持以下功能:
- 語法高亮
- 標(biāo)記表達(dá)式的語法錯誤
- XML代碼補(bǔ)全
- 引用,包括navigation(如導(dǎo)航到聲明處)以及快速文檔查詢
注意: 數(shù)組以及
通用類型
空厌,比如說Observable
類庐船,可能會顯示錯誤事實上并沒有錯誤。
預(yù)覽面板會顯示數(shù)據(jù)綁定的默認(rèn)值嘲更。在以下例子中筐钟,面板會在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á)數(shù)值赋朦,參考:Designtime Layout Attributes
---------------------<完>-----------------------
原文鏈接:Data Binding Guide (Android)
(如有翻譯有誤或者不理解的地方篓冲,請指正)