數(shù)據(jù)綁定布局
數(shù)據(jù)綁定布局文件以根標(biāo)記layout開頭,后跟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>
數(shù)據(jù)對(duì)象
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;
}
}
可觀察性
可觀察性是指一個(gè)對(duì)象將其數(shù)據(jù)變化通知給其他對(duì)象的能力。通過數(shù)據(jù)綁定庫,您可以讓對(duì)象、字段或集合變?yōu)榭捎^察。
當(dāng)其中一個(gè)可觀察數(shù)據(jù)對(duì)象綁定到界面并且該數(shù)據(jù)對(duì)象的屬性發(fā)生更改時(shí),界面會(huì)自動(dòng)更新。
可觀察字段
在創(chuàng)建實(shí)現(xiàn)Observable接口的類時(shí)要完成一些操作,但如果您的類只有少數(shù)幾個(gè)屬性,則這樣操作的意義不大。在這種情況下,您可以使用通用Observable類和以下特定于基元的類,將字段設(shè)為可觀察字段:ObservableBooleanObservableByteObservableCharObservableShortObservableIntObservableLongObservableFloatObservableDoubleObservableParcelable
可觀察字段是具有單個(gè)字段的自包含可觀察對(duì)象。原語版本避免在訪問操作期間封箱和開箱。要使用此機(jī)制,請(qǐng)采用 Java 編程語言創(chuàng)建 public final 屬性,或在 Kotlin 中創(chuàng)建只讀屬性,如以下示例所示:
private static class User {
public final ObservableField<String> firstName = new ObservableField<>();
public final ObservableField<String> lastName = new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
- 可觀察觀察集合
某些應(yīng)用使用動(dòng)態(tài)結(jié)構(gòu)來保存數(shù)據(jù)。可觀察集合允許使用鍵訪問這些結(jié)構(gòu)。當(dāng)鍵為引用類型(如String)時(shí),ObservableArrayMap類非常有用,如以下示例所示:
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
在布局中,可使用字符串鍵找到地圖,如下所示:
<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ù)時(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"/>
- 可觀察對(duì)象
實(shí)現(xiàn)Observable接口的類允許注冊(cè)監(jiān)聽器,以便它們接收有關(guān)可觀察對(duì)象的屬性更改的通知。
Observable接口具有添加和移除監(jiān)聽器的機(jī)制,但何時(shí)發(fā)送通知?jiǎng)t必須由您決定。為便于開發(fā),數(shù)據(jù)綁定庫提供了用于實(shí)現(xiàn)監(jiān)聽器注冊(cè)機(jī)制的BaseObservable類。實(shí)現(xiàn)BaseObservable的數(shù)據(jù)類負(fù)責(zé)在屬性更改時(shí)發(fā)出通知。具體操作過程是向 getter 分配Bindable注釋,然后在 setter 中調(diào)用notifyPropertyChanged()方法,如以下示例所示:
private static class User extends BaseObservable {
private String firstName;
private String lastName;
@Bindable
public String getFirstName() {
return this.firstName;
}
@Bindable
public String getLastName() {
return this.lastName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
public void setLastName(String lastName) {
this.lastName = lastName;
notifyPropertyChanged(BR.lastName);
}
}
綁定數(shù)據(jù)
系統(tǒng)會(huì)為每個(gè)布局文件生成一個(gè)綁定類。默認(rèn)情況下,類名稱基于布局文件的名稱,此類包含從布局屬性(例如,user 變量)到布局視圖的所有綁定,并且知道如何為綁定表達(dá)式指定值。建議的綁定創(chuàng)建方法是在擴(kuò)充布局時(shí)創(chuàng)建,如以下示例所示:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
User user = new User("Test", "User");
binding.setUser(user);
}
或者,您可以使用LayoutInflater獲取視圖,如以下示例所示:
ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
如果您要在Fragment,ListView 或RecyclerView適配器中使用數(shù)據(jù)綁定項(xiàng),您可能更愿意使用綁定類或DataBindingUtil類的inflate()方法,如以下代碼示例所示:
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
// or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
方法引用、監(jiān)聽綁定
方法引用和監(jiān)聽器綁定之間的主要區(qū)別在于實(shí)際監(jiān)聽器實(shí)現(xiàn)是在綁定數(shù)據(jù)時(shí)創(chuàng)建的,而不是在事件觸發(fā)時(shí)創(chuàng)建的。如果您希望在事件發(fā)生時(shí)對(duì)表達(dá)式求值,則應(yīng)使用監(jiān)聽器綁定。
- 方法引用
在方法引用中,方法的參數(shù)必須與事件監(jiān)聽器的參數(shù)匹配。
public class MyHandlers {
public void onClickFriend(View view) { ... }
}
綁定表達(dá)式可將視圖的點(diǎn)擊監(jiān)聽器分配給 onClickFriend() 方法,如下所示:
<?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>
- 監(jiān)聽綁定
在監(jiān)聽器綁定中,只有您的返回值必須與監(jiān)聽器的預(yù)期返回值相匹配(預(yù)期返回值無效除外)。例如,請(qǐng)參考以下具有 onSaveClick() 方法的 presenter 類:
public class Presenter {
public void onSaveClick(Task task){}
}
然后,您可以將點(diǎn)擊事件綁定到 onSaveClick() 方法,如下所示:
<?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>
綁定適配器
數(shù)據(jù)綁定庫允許您通過使用適配器指定為設(shè)置值而調(diào)用的方法、提供您自己的綁定邏輯,以及指定返回對(duì)象的類型。
- 自動(dòng)選擇方法
對(duì)于名為 example 的特性,庫自動(dòng)嘗試查找接受兼容類型作為參數(shù)的方法 setExample(arg)。系統(tǒng)不會(huì)考慮特性的命名空間,搜索方法時(shí)僅使用特性名稱和類型。
以 android:text="@{user.name}" 表達(dá)式為例,庫會(huì)查找接受 user.getName() 所返回類型的 setText(arg) 方法。如果 user.getName() 的返回類型為 String,則庫會(huì)查找接受 String 參數(shù)的 setText() 方法。如果表達(dá)式返回的是 int,則庫會(huì)搜索接受 int 參數(shù)的 setText() 方法。表達(dá)式必須返回正確的類型,您可以根據(jù)需要強(qiáng)制轉(zhuǎn)換返回值的類型。
即使不存在具有給定名稱的特性,數(shù)據(jù)綁定也會(huì)起作用。然后,您可以使用數(shù)據(jù)綁定為任何 setter 創(chuàng)建特性。例如,支持類DrawerLayout 沒有任何特性,但有很多 setter。以下布局會(huì)自動(dòng)將setScrimColor(int) 和setDrawerListener(DrawerListener)) 方法分別用作 app:scrimColor 和 app:drawerListener 特性的 setter:
<android.support.v4.widget.DrawerLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:scrimColor="@{@color/scrim}"
app:drawerListener="@{fragment.drawerListener}">
- 指定自定義方法名稱
某些特性擁有名稱不符的 setter。在這些情況下,某個(gè)特性可能會(huì)使用BindingMethods 注釋與 setter 相關(guān)聯(lián)。注釋與類一起使用,可以包含多個(gè)BindingMethod注釋,每個(gè)注釋對(duì)應(yīng)一個(gè)重命名的方法。綁定方法是可添加到應(yīng)用中任何類的注釋。在以下示例中,android:tint 特性與setImageTintList(ColorStateList) 方法相關(guān)聯(lián),而不與 setTint() 方法相關(guān)聯(lián):
@BindingMethods({
@BindingMethod(type = "android.widget.ImageView",
attribute = "android:tint",
method = "setImageTintList"),
})
大多數(shù)情況下,您無需在 Android 框架類中重命名 setter。特性已使用命名慣例實(shí)現(xiàn),可自動(dòng)查找匹配的方法。
- 提供自定義邏輯
某些特性需要自定義綁定邏輯。例如,android:paddingLeft 特性沒有關(guān)聯(lián)的 setter,而是提供了 setPadding(left, top, right, bottom) 方法。使用BindingAdapter注釋的靜態(tài)綁定適配器方法支持自定義特性 setter 的調(diào)用方式。
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());
}
參數(shù)類型非常重要。第一個(gè)參數(shù)用于確定與特性關(guān)聯(lián)的視圖類型,第二個(gè)參數(shù)用于確定在給定特性的綁定表達(dá)式中接受的類型。
您還可以使用接收多個(gè)特性的適配器。如果 ImageView 對(duì)象同時(shí)使用了 imageUrl 和 error,并且 imageUrl 是字符串,error 是Drawable,則會(huì)調(diào)用適配器。
如以下示例所示:
@BindingAdapter({"imageUrl", "error"})
public static void loadImage(ImageView view, String url, Drawable error) {
Picasso.get().load(url).error(error).into(view);
}
<ImageView app:imageUrl="@{venue.imageUrl}" app:error="@{@drawable/venueError}" />
如果您希望在設(shè)置了任意特性時(shí)調(diào)用適配器,則可以將適配器的可選 requireAll 標(biāo)記設(shè)置為 false,如以下示例所示:
@BindingAdapter(value={"imageUrl", "placeholder"}, requireAll=false)
public static void setImageUrl(ImageView imageView, String url, Drawable placeHolder) {
if (url == null) {
imageView.setImageDrawable(placeholder);
} else {
MyImageLoader.loadInto(imageView, url, placeholder);
}
}