1. 程式人生 > >Android Data Binding(資料繫結)使用者指南

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(PO​​JO)可用於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