本文為官網(wǎng)文章翻譯 原文地址
這篇文檔將展示如何使用 Data Binding 庫聲明布局以及如何最低化應用邏輯和布局之間的耦合胸嘴。
Data Binding Library 非常靈活并且具有很高的兼容性---對于這個兼容庫宴树,你可以使用Android2.1以上的所有版本的Android平臺。
為了使用 data binding,Gradle的 android插件需要是1.5.0-alpha1或者更高。
構建環(huán)境
為了使用 Data Binding御蒲,通過sdk manager下載 support repository 中的依賴庫签财。
為了配置app應用 data binding间唉,在 app module 的 build.gradle 文件中,添加 dataBinding 元素施逾。
使用下面的代碼片段進行data binding 的配資:
android{
....
dataBinding{
enabled=true
}
}
如果你的app module依賴于一個使用了 data binding 的庫敷矫,你的 app module 也必須在build.gradle 文件中配置 data binding。
同樣汉额,你需要確認你的android studio 應該不低于1.3
Data Binding 布局文件
第一個data binding 表達式曹仗。
data binding布局文件和普通布局文件稍微有些不同。它以layout為根標簽(root tag)蠕搜,隨后緊跟data元素和一個view的根元素怎茫。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中user變量描述了一個會在布局中用到的屬性讥脐。
<variable name="user" type="com.example.User"/>
在布局文件中遭居,表達式使用"@{}"語法描述屬性特征啼器。下面是一個示例旬渠,將TextView的text屬性設置為設置用戶名。
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
Data 對象
假設有一個User普通類
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ù)不能夠改變端壳。在一個應用中告丢,數(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的text屬性。對于前面的類來說颅湘,通過直接訪問 firstName 屬性的方式话侧;對于后面的類來說,通過getFirstName()的方法闯参。當然瞻鹏,也可以通過firstName()來解決這個問題,如果這個方法存在的話鹿寨。
綁定數(shù)據(jù)
默認的新博,綁定類將被創(chuàng)建,它的名字以布局文件名為基礎脚草,遵循Pascal原則赫悄,以"Binding"為后綴。比如馏慨,上面的布局文件名為 main_activity.xml埂淮,因此將產(chǎn)生的類就是MainActivityBinding。這個類持有所有從特征(比如user變量)到布局view的綁定熏纯,并且知道怎樣為綁定表達式賦值同诫。創(chuàng)建綁定的最簡單的方式,就是在它被inflating的時候樟澜。
@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);
}
完成误窖。另外,可以通過下面的方式來得到view:
MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());
View view = binding.getRoot();
如果你在一個ListView或者RecyclerView的adapter中使用數(shù)據(jù)綁定秩贰,推薦使用:
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
事件處理
Data Binding允許寫表達式來處理view事件(比如onClick)霹俺。事件屬性名和監(jiān)聽器(listener)方法一致(也有一些例外)。舉個栗子毒费,View.OnLongClickListener 有 onLongClick()方法丙唧,所以,該事件的屬性名為 android:onLongClick觅玻。屬性名所對應的想际,就是該事件的處理方法。下面介紹兩種方式來處理事件:
- 方法引用(Method References):在表達式中溪厘,可以引用方法胡本,該方法應該遵守監(jiān)聽器方法簽名的特征(譯者:比如參數(shù)個數(shù)、參數(shù)類型畸悬、返回值類型侧甫、訪問限制等)。當一個表達式被認為是對一個方法的引用,Data Binding 將把這個引用方法和所有者對象包裹進一個監(jiān)聽器中披粟,并將該監(jiān)聽器設置到目標view上咒锻。
- 監(jiān)聽綁定(Listener Bindings):當事件發(fā)生時,蘭姆達表達式將被執(zhí)行守屉。Data Binding會在view上創(chuàng)建一個監(jiān)聽器惑艇,當事件被分發(fā)下來,監(jiān)聽器將計算蘭姆達表達式拇泛。
方法引用
事件可以直接和事件處理器關聯(lián)綁定敦捧,就是android:onClick被指派到Activity中的onClick方法一樣。和View#onClick屬性相比碰镜,方法引用的優(yōu)勢在于兢卵,表達式將在編譯的時候進行處理,因此绪颖,如果方法不存在或者簽名不正確秽荤,將報編譯期錯誤。
方法引用和監(jiān)聽器綁定主要的不同在于柠横,方法引用中窃款,真正的監(jiān)聽的實現(xiàn)是在數(shù)據(jù)被綁定的時候,而不是在事件被觸發(fā)的時候牍氛。如果你更喜歡在事件發(fā)生的時候計算表達式晨继,應該是用監(jiān)聽綁定(listener binding)。
為了給事件指定處理器handler搬俊,應該使用一般的綁定的表達式紊扬,它的值就是處理事件的方法名。舉個栗子
public class MyHandlers {
public void onClickFriend(View view) { ... }
}
綁定表達式給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.Handlers"/>
<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>
注意唉擂,表達式中方法的簽名應該和監(jiān)聽器對象中的方法簽名保持匹配餐屎。
監(jiān)聽綁定
監(jiān)聽表達式將在事件發(fā)生的時候綁定事件。這種綁定方式和方法引用類似玩祟,但是腹缩,這種綁定允許你運行任意的綁定表達式。這個屬性需要gradle版本2.0以上空扎。
在方法引用中藏鹊,方法的參數(shù)必須匹配事件監(jiān)聽器方法。在監(jiān)聽器綁定中转锈,僅僅只需要返回值和監(jiān)聽器方法的返回值匹配即可盘寡。舉個栗子
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)聽器被表現(xiàn)為蘭姆達表達式,蘭姆達表達式必須作為整個表達式的根元素黑忱。當在表達式中使用回調(diào)函數(shù)宴抚,Data Binding將自動創(chuàng)建必要的監(jiān)聽并注冊在事件上。當view上的事件發(fā)生甫煞,Data Binding將計算給定的表達式菇曲。
注意,在上面的栗子中抚吠,我們并沒有定義要傳入到onClick(View view)中的view參數(shù)常潮。監(jiān)聽器綁定對于監(jiān)聽器參數(shù)提供了兩種選擇:如果不需要監(jiān)聽器(各種listener)參數(shù),你可以選擇忽略方法中的所有參數(shù)不寫楷力,如果需要使用時喊式,應該寫出使用所有監(jiān)聽器參數(shù)。舉個栗子:
android:onClick="@{(view) -> presenter.onSaveClick(task)}"
android:onClick="@{() -> presenter.onSaveClick(task)}"
第一種寫了監(jiān)聽器參數(shù)萧朝,第二種沒有寫岔留,但是它們是等價的,因為在事實上检柬,處理方法onSavaClick中并沒有需要view献联。
如果你想在表達式中使用監(jiān)聽器參數(shù),可以這樣寫:
public class Presenter {
public void onSaveClick(View view, Task task){}
}
android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"
你可以在蘭姆達表達式中使用多個參數(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)}" />
如果監(jiān)聽函數(shù)沒有返回void何址,你的表達式要注意返回值的一致性.例如里逆,如果你想監(jiān)聽一個long click事件,你的表達式應該返回一個布爾值:
public class Presenter {
public boolean onLongClick(View view, Task task){}
}
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"
如果表達式因為null對象而不能進行計算用爪,Data Binding 會返回該類型下默認的Java值原押。對于引用類型是null,對于int就是0偎血,對于布爾值就是false诸衔。
Data Objects
任何普通對象(POJO)都可以用來進行數(shù)據(jù)綁定。但是修改一個POJO對象并不能觸發(fā)UI的更新颇玷。數(shù)據(jù)綁定真正的力量在于當數(shù)據(jù)更改時UI層能得到更新署隘。這里,將提供三種不同的數(shù)據(jù)更改通知機制:Observable objects, observable fields 和 ovservable collections.
當這些之中的一個任意一個observable 數(shù)據(jù)對象被綁定到 UI亚隙,當數(shù)據(jù)對象發(fā)生變化時磁餐,UI將得到自動更新。
Observable Objects
當一個類實現(xiàn)了Observable接口阿弃,這將
Observable接口有一種機制增添和刪除listener诊霹,但是,nofitying取決于開發(fā)者渣淳。為了簡化開發(fā)工作脾还,一個基類,BaseObservable被創(chuàng)建用來實現(xiàn)listener的注冊機制入愧。而數(shù)據(jù)類只需要負責當屬性發(fā)生的變化的時候進行通知鄙漏。這個做法嗤谚,通過對getter方法進行Bindable注解,在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注解會在編輯期間產(chǎn)生進入BR類文件的入口巩步。BR類文件被創(chuàng)建在module package中。
ObservableFields
創(chuàng)建一個Observable對象需要做一些工作桦踊,因此椅野,如果開發(fā)者想要節(jié)省時間或者需要更改的屬性很少,就可以使用ObservableField籍胯。相似的竟闪,還有ObserableBoolean,ObserableByte,ObservableChar,ObservableShort,ObservableInt,ObserableLong,ObserableFloat,ObservableDouble,ObservableParcelable等。ObservableFields 是一種自包含的被觀察者對象杖狼,擁有一個field炼蛤。
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和個體方法:
user.firstName.set("Google");
int age = user.age.get();
Observable Collections
有些應用使用更加動態(tài)的結(jié)構來持有數(shù)據(jù)蝶涩。Observable collections允許通過鍵值對訪問這些數(shù)據(jù)鲸湃。當鍵為引用類型時,ObservableArrayMap將是非常有用的子寓。比如暗挑,當key為字符串時:
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
在布局文件中,可以通過key來訪問數(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"/>
當key為Integer時斜友,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"/>