Android Data Binding(資料繫結)使用者指南
轉自:https://www.jianshu.com/p/b1df61a4df77
1. 介紹
這篇文章介紹瞭如何使用Data Binding庫來寫宣告的layouts檔案,並且用最少的程式碼來繫結你的app邏輯和layouts檔案。Data Binding庫不僅靈活而且廣泛相容- 它是一個support庫,因此你可以在所有的Android平臺最低能到Android 2.1(API等級7+)上使用它。
需求:Android Plugin for Gradle **1.5.0-alpha1 **或 更高版本。
2. 構建環境
要開始使用Data Binding,首先需要在Android SDK Manager的支援庫裡下載該庫。
你的app要使用Data Binding,需要新增Data Binding到gradle構建檔案裡,如下:
android {
....
dataBinding {
enabled = true
}
}
Data Binding外掛將會在你的專案內新增必需提供的以及編譯配置依賴。請確保您使用的是Android Studio的相容版本。Android Studio的Data Binding外掛需要Android Studio **1.3.0 **或 更高版本。
3. Data Binding Layout檔案
3.1 Data Binding表示式
Data Binding layout檔案有點不同的是:起始根標籤是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內描述了一個名為user的變數屬性,使其可以在這個layout中使用:
<variable name="user" type="com.example.User"/>
在layout的屬性表示式寫作@{},下面是一個TextView的text設定為user的firstName屬性:
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
3.2 Data物件
假設你有一個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;
}
}
這個型別的物件擁有從不改變的資料。在app中它是常見的,可以讀取一次並且之後從不改變。當然也可以使用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屬性的表示式@{user.firstName}將訪問前者POJO物件中的firstName和後者JavaBeans物件中的getFirstName()方法。
3.3 Binding資料
預設情況下,一個Binding類會基於layout檔案的名稱而產生,將其轉換為Pascal case(譯註:首字母大寫的命名規範)並且新增“Binding”字尾。上述的layout檔案是main_activity.xml,因此生成的類名是MainActivityBinding。此類包含從layout屬性到layout的Views中所有的bindings(例如user變數),並且它還知道如何給Binding表示式分配數值。建立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);
}
就是這樣,執行app後,你將會看到Test User。或者你可以通過如下獲取View:
@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);
}
如果你在ListView或者RecyclerView adapter使用Data Binding時,你可能會使用:
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup,
false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
3.4 事件處理
資料繫結允許你編寫表示式來處理view分派的事件。事件屬性名字取決於監聽器方法名字。例如View.OnLongClickListener有onLongClick()的方法,因此這個事件的屬性是android:onLongClick。處理事件有兩種方法:
- Method References
- Listener Bindings
4. 深入Layout檔案
4.1 Import
零個或多個import元素可能在data元素中使用。這些只用在你的layout檔案中新增引用,就像在Java中:
<data>
<import type="android.view.View"/>
</data>
現在,View可以使用你的Binding表示式:
<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
當類名有衝突時,其中一個類名可以重新命名為alias::
<import type="android.view.View"/>
<import type="com.example.real.estate.View"
alias="Vista"/>
這樣,在該layout檔案中Vista對應com.example.real.estate.View,而View對應android.view.View。匯入的型別可以在Variable和表示式中使用作為引用來使用:
<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,所以自動匯入Variable在你的IDE不能使用。您的app仍會正常編譯,你可以在您的Variable定義中使用完全符合規定的名稱來解決該IDE問題。
<TextView
android:text="@{((User)(user.connection)).lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
匯入的型別還可以在表示式中使用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。*是自動匯入的。
4.2 Variables
在data中可以使用任意數量的variable元素。每一個variable元素描述了一個用於layout檔案中Binding表示式的屬性。
<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實現了Observable或observable collection,這應該反映在型別中。(譯註:需要查詢資料來理解)如果variable是一個沒有實現Observable介面的基本類或者介面,Variables不會被observed!當對於多種配置有不同的layout檔案時(如,橫向或縱向),Variables會被合併。這些layout檔案之間必須不能有衝突的Variable定義。
產生的Binding類對於每一個描述的Variables都會有setter和getter。這些Variables會使用預設的Java值 - null(引用型別)、0(int)、false(boolean)等等,直到呼叫setter時。
4.3 自定義Binding類名稱
預設情況下,Binding類的命名是基於所述layout檔案的名稱,用大寫開頭,除去下劃線()以及()後的第一個字母大寫,然後新增“Binding”字尾。這個類將被放置在一個模組封裝包裡的databinding封裝包下。例如,所述layout檔案contact_item.xml將生成ContactItemBinding。如果模組包是com.example.my.app,那麼它將被放置在com.example.my.app.databinding。
Binding類可通過調整data元素中的class屬性來重新命名或放置在不同的包中。例如:
<data class="ContactItem">
...
</data>
在模組封裝包的databinding包中會生成名為ContactItem的Binding類。如果要想讓該類生成在不同的包種,你需要新增字首.,如下:
<data class=".ContactItem">
...
</data>
在這個情況下,ContactItem類直接在模組包種生成。或者你可以提供整個包名:
<data class="com.example.ContactItem">
...
</data>
4.4 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
4.5 表示式
常用表示式跟Java表示式很像,以下這些是一樣的:
- 描述 - | - 運算子 - |
數學 | + - * / % |
字串連線 | + |
邏輯 | && || |
二進位制 | & |
一元運算 | + - ! ~ |
移位 | >> >>> <<< |
比較 | == > < >= <= |
例項 | instanceof |
分組 | () |
空 | null |
型別轉換 | cast |
方法呼叫 | . |
資料訪問 | [] |
三元運算 | ?: |
示例:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
- 缺少的操作:
- this
- super
- new
- 顯式泛型呼叫
- Null合併操作
?? - 左邊的物件如果它不是null,選擇左邊的物件;或者如果它是null,選擇右邊的物件:
android:text="@{user.displayName ?? user.lastName}"
函式上的寫法如下:
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
- 屬性引用
第一個已經在前邊提到了a)Data Binding表示式:JavaBean引用的簡短格式。
當一個表示式引用一個類的屬性,它仍使用同樣的格式對於欄位、getters以及ObservableFields。
android:text="@{user.lastName}"
-
避免 NullPointerException
Data Binding程式碼生成時自動檢查是否為nulls來避免出現null pointer exceptions錯誤。例如,在表示式@{user.name}中,如果user是null,user.name會賦予它的預設值(null)。如果你引用user.age,age是int型別,那麼它的預設值是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]}"
- 字串
當使用單引號包含屬性值時,在表示式中使用雙引號很容易:
android:text=’@{map[“firstName”]}’
使用雙引號來包含屬性值也是可以的。字串前後需要使用"`":
android:text="@{map[`firstName`]}"
android:text="@{map["firstName"]}"
- Resources
使用正常的表示式來訪問resources也是可行的:
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
格式化字串和複數可以通過提供引數來判斷
android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"
當複數需要多個引數時,所有的引數都會通過:
Have an orange
Have %d oranges
android:text="@{@plurals/orange(orangeCount, orangeCount)}"
一些資源需要顯式型別判斷:
型別 | 正常引用 | 表示式引用 |
String[] | @array | |
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不會導致UI更新。Data Binding的真正能力是當資料變化時,可以通知給你的Data物件。有三種不同的資料變化通知機制:Observable物件、ObservableFields以及observable collections。當這些可觀察Data物件繫結到UI,Data物件屬性的更改後,UI也將自動更新。
5.1 Observable 物件
實現android.databinding.Observable介面的類可以允許附加一個監聽器到Bound物件以便監聽物件上的所有屬性的變化。Observable介面有一個機制來新增和刪除監聽器,但通知與否由開發人員管理。為了使開發更容易,一個BaseObservable的基類為實現監聽器註冊機制而建立。Data實現類依然負責通知當屬性改變時。這是通過指定一個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 getFirstName() {
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類檔案會在模組包內生成。如果用於Data類的基類不能改變,Observable介面通過方便的PropertyChangeRegistry來實現用於儲存和有效地通知監聽器。
5.2 Observable 欄位
一些小工作會涉及到建立Observable類,因此那些想要節省時間或者幾乎沒有幾個屬性的開發者可以使用ObservableFields。ObservableFields是自包含具有單個欄位的observable物件。它有所有基本型別和一個是引用型別。要使用它需要在data物件中建立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();
5.3 Observable 集合
一些app使用更多的動態結構來儲存資料。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用於鍵是整數:
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類都擴充套件了android.databinding.ViewDataBinding。
6.1 建立
Binding應在inflation之後就立馬建立,以確保View層次結構不在之前打擾layout中的binding到views上的表示式。有幾個方法可以繫結到一個layout。最常見的是在Binding類上使用靜態方法.inflate方法載入View的層次結構並且繫結到它只需這一步。還有一個更簡單的版本,只需要L