Data Binding (中文文檔)

Data Binding 類庫

這篇文檔將教你如何運用 Data Binding 類庫來編寫聲明試佈局,而且儘可能減小粘合代碼對你的應用邏輯和佈局上的綁定。 Data Binding 是一種靈活和普遍兼容的類庫,它是一個支持庫,所以你能夠在任何 Android 2.1(API level 7+) 以上的設備 使用。 爲了使用 Data Binding,Android Gradle 插件版本必須爲 1.5.0-alpha1 或以上,查看 如何升級你的 Gradle 插件html

構建環境

爲了獲取 Data Binding,去 Android SDK manager 下載 它的支持庫。 在你的應用 module 的 build.gradle 添加 dataBinding 來讓你的應用支持 Data Binding。 用如下代碼片斷來配置 Data Binding:java

android {
    ....
    dataBinding {
        enabled = true
    }
}
複製代碼

若你有一個應用 module 用了一個依賴了 Data Binding 的類庫,也同樣要在該 module 中配置開啓 Data Binding。 另外,若是想使用 Data Binding,大家你的 Android Studio 版本必須等於或大於 1.3。android


Data Binding 佈局文件

編寫你的第一個 Data Binding 表達式

Data Binding 的佈局文件有一點不同,它以 layout 標籤做爲根標籤,而且有一個data 元素和 一個 view 元素做爲子標籤,這個 view 元素就是你沒有使用 Data Binding 時該有的佈局文件。如下是一個例子:api

<?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 標籤下的 variable 是你在這個 Data Binding 佈局文件中有可能使用到的對象。數組

<variable name="user" type="com.example.User"/>
複製代碼

佈局中使用 @{} 語法來包裹 variable 中的對象屬性,在下面例子中,TextViewtext 屬性的值用 userfirstName 屬性來替代。安全

<TextView android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="@{user.firstName}"/>
複製代碼

數據對象

如今讓咱們假設你有一個普通的 Java 對象(POJO)Userbash

public class User {
   public final String firstName;
   public final String lastName;
   public User(String firstName, String lastName) {
       this.firstName = firstName;
       this.lastName = lastName;
   }
}
複製代碼

這個對象屬性(final 修飾)是不可變的,若是你的數據對象只提供只讀權限而且以後不會再去修改的話,這種作法很廣泛。咱們也能夠用 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;
   }
}
複製代碼

從數據綁定的角度來看,這兩個類是等價的。TextViewandroid:text 屬性值會經過表達式 @{user.firstName} 來獲取第一個類中的 fistName 字段值,活着獲取第二個類中的 getFirstName() 方法返回的值。另外,若是 firstName() 方法存在的話也是能夠獲取到值的。app

綁定數據

默認狀況下,將根據佈局文件的名稱生成一個綁定類,將其轉換爲 Pascal 格式並將 Binding 做爲其後綴。上面的佈局文件是名稱 main_activity.xml ,所以生成的綁定類是 MainActivityBinding。這個類將佈局屬性(例如用戶變量)綁定到佈局的視圖中,並知道如何經過表達式來賦值。建立綁定類的最簡單方式是在視圖 inflate 的時候:框架

@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);
}
複製代碼

完成了!運行這個應用,你會在界面中看到測試的 User。另外,你能夠經過一些方 式獲取綁定類:

MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());
複製代碼

若是你在 ListView 或者 RecyclerView 中使用數據綁定電話,你能夠經過一些方式獲取綁定類:

ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
複製代碼

事件處理

數據綁定容許你編寫表達式來處理從視圖中分派的事件(例如 onClick)。除少數例外,事件屬性名稱由偵聽器中的方法名稱來肯定。例如,View.OnLongClickListener 有一個 onLongClick()方法,因此這個事件的屬性是 android:onLongClick。有如下兩種方式來處理一個事件。

  • 方法引用:在表達式中,能夠引用符合偵聽器方法簽名的方法。 當表達式被評估爲方法引用時,數據綁定將方法引用和全部者對象包裝在偵聽器中,並將該偵聽器設置在目標視圖上。 若是表達式被評估爲 null,則數據綁定不會建立偵聽器,而是設置一個空的偵聽器。
  • 監聽器綁定:當事件發生時,lambda 表達式將被評估。 數據綁定老是會在視圖上建立一個監聽器。 當事件被髮送時,監聽器將評估 lambda 表達式。

方法引用

事件能夠直接綁定處處理的方法中,相似於 android:onClick 能夠做爲 Activity 的一個方法同樣。與 View#onClick 屬性相比,一個主要的優勢是表達式在編譯時被處理,所以若是方法不存在或者它的簽名不正確,就會收到編譯時錯誤。

方法引用和監聽器綁定的主要區別在於實際的監聽器實現是在綁定數據時建立的,而不是在事件觸發時建立的。

要將事件分配給其處理程序,請使用常規綁定表達式,其值是要調用的方法名稱。 例如,若是你的數據對象有兩個方法:

public class MyHandlers {
    public void onClickFriend(View view) { ... }
}
複製代碼

綁定表達式能夠爲 View 分配一個點擊監聽器:

<?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>
複製代碼

請注意,表達式中方法的簽名必須與監聽器對象中方法的簽名徹底匹配。

監聽器綁定

監聽器綁定是事件發生時運行的綁定表達式。相似於方法引用,可是容許你運行任意的數據綁定表達式。 此功能適用於 Gradle 2.0 版及更高版本的 Android Gradle 插件。

在方法引用中,方法的參數必須與事件偵聽器的參數匹配。 在監聽器綁定中,只有你的返回值必須與監聽器的指望返回值相匹配(除非它返回值爲 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 表達式做爲根元素。 當表達式中有回調時,數據綁定會自動爲事件建立必要的偵聽器和註冊表。 當視圖觸發事件時,數據綁定將評估給定的表達式。 就像在常規的綁定表達式同樣,當這些監聽器表達式被評估的時候,你仍然能夠獲取數據綁定的空值和保證線程安全。

請注意,在上面的例子中,咱們沒有定義傳入 onClick(android.view.View) 的視圖參數。 監聽器綁定爲監聽器參數提供了兩個選擇:您能夠忽略該方法的全部參數或將其所有命名。 若是您想要命名參數,則能夠在表達式中使用它們。 例如,上面的表達式能夠寫成:

android:onClick="@{(view) -> presenter.onSaveClick(task)}"
複製代碼

或者若是你想使用表達式中的參數,能夠像下面這樣:

public class Presenter {
    public void onSaveClick(View view, Task task){}
}
複製代碼
android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"
複製代碼

你能夠在 lambda 表達式中使用多個參數:

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)}" />
複製代碼

若是正在偵聽的事件返回值不是 void,則表達式必須返回相同類型的值。 例如,若是你想監聽長按事件,你的表達式應該返回布爾值。

public class Presenter {
    public boolean onLongClick(View view, Task task){}
}
複製代碼
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"
複製代碼

若是因爲空對象而沒法評估表達式,Data Binding 將返回該類型的默認 Java 值。 例如,引用類型爲 nullint0booleanfalse 等等。

若是您須要使用帶謂詞的表達式(例如三元),則可使用 void 做爲符號。

android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"
複製代碼

避免複雜的監聽器

監聽器表達式很是強大,可讓你的代碼變得很是容易閱讀。 另外一方面,包含複雜表達式的監聽器也會使您的佈局難以閱讀和維護。這些表達式應該像從 UI 中傳遞可用數據到回調方法同樣簡單。你應該從偵聽器表達式調用的回調方法內實現業務邏輯。 存在一些專門的單擊事件處理程序,它須要除 android:onClick 以外的其餘屬性以免衝突。 已建立了如下屬性以免這種衝突:

Class Listener Setter Attribute
SearchView setOnSearchClickListener(View.OnClickListener) android:onSearchClick
ZoomControls setOnZoomInClickListener(View.OnClickListener) android:onZoomIn
ZoomControls setOnZoomOutClickListener(View.OnClickListener) android:onZoomOut

佈局文件細節

Imports

數據元素內可使用零個或多個 import 元素。 這些就像在 Java 中同樣能夠輕鬆地引用類到你的佈局文件中。

<data>
    <import type="android.view.View"/>
</data>
複製代碼

如今 View 類能夠在你的綁定表達式中使用了。

<TextView
   android:text="@{user.lastName}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
複製代碼

若是類名有衝突的話,其中一個類則需起別名了。

<import type="android.view.View"/>
<import type="com.example.real.estate.View" alias="Vista"/>
複製代碼

如今,在佈局文件中,Vista 被看成 com.example.real.estate.View 引入,View 被看成 android.view.View 引入。 導入的類型能夠用做變量和表達式中的類型引用:

<data>
    <import type="com.example.User"/>
    <import type="java.util.List"/>
    <variable name="user" type="User"/>
    <variable name="userList" type="List&lt;User&gt;"/>
</data>
複製代碼

注意:Android Studio 還沒有處理導入,所以自動導入變量在你的的 IDE 中可能沒法完成。 你的應用程序仍然能夠正常編譯,你能夠經過在變量定義中使用徹底限定的名稱來解決 IDE 的這個問題。

<TextView
   android:text="@{((User)(user.connection)).lastName}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
複製代碼

當在表達式中引用靜態字段和方法時,也可使用導入的類型:

<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.* 會被自動導入。

Variables

data 元素內可使用任意的 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>
複製代碼

變量類型在編譯時被檢查,因此若是一個變量實現了 Observable 或者一個 observable collection,那麼它應該被反映在類型中。 若是變量是沒有實 Observable 接口的基類或接口,那麼它將不會被觀察!

當不一樣的配置(例如橫向或縱向)有不一樣的佈局文件時,變量將被合併。 這些佈局文件之間不得有衝突的變量定義。

生成的綁定類將爲每一個描述的變量設置一個 settergetter 方法。 變量將採用默認的 Java 值,直到調用 setter 爲止 。對於引用類型爲 null,對於 int0,對於 booleanfalse 等。

自定義綁定類的名字

默認狀況下,根據佈局文件的名稱生成一個綁定類,以大寫字母開頭,刪除下劃線(_)並以後的單詞首字母大寫,而後添加後綴 Binding。 這個類將被放置在模塊包下的數據綁定包中。 例如,佈局文件 contact_item.xml 將生成 ContactItemBinding。 若是模塊包是 com.example.my.app,那麼它將被放置在 com.example.my.app.databinding 中。

綁定類能夠經過調整 data 元素的 class 屬性來重命名或放置在不一樣的包中。 例如:

<data class="ContactItem">
    ...
</data>
複製代碼

這會在模塊包中的數據綁定包中生成綁定類 ContactItem。 若是該類應該在模塊包中的其餘包中生成,則能夠用「.」做爲前綴:

<data class=".ContactItem">
    ...
</data>
複製代碼

在這種狀況下,直接在模塊包中生成了 ContactItem。 若是提供完整的包,則可使用任意的包:

<data class="com.example.ContactItem">
    ...
</data>
複製代碼

Includes

經過在屬性中使用應用程序命名空間和變量名稱,變量能夠從包含的佈局中傳遞到包含的佈局的綁定中:

<?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.xmlcontact.xml 佈局文件中都必須有一個 user 變量。

數據綁定不支持 include 做爲 merge 元素的直接子元素。 例如,不支持如下佈局:

<?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>
複製代碼

表達式語言

共同特徵

表達式語言看起來很像 Java 表達式。 這些是同樣的:

  • Mathematical + - / * %
  • String concatenation +
  • Logical && ||
  • Binary & | ^
  • Unary + - ! ~
  • Shift >> >>> <<
  • Comparison == > < >= <=
  • instanceof
  • Grouping ()
  • Literals - character, String, numeric, null
  • Cast
  • Method calls
  • Field access
  • Array access []
  • Ternary operator ?: 例如:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
複製代碼

缺乏的操做

你在 Java 中使用的一些表達式語法並不支持綁定操做。

  • this
  • super
  • new
  • 明確的泛型調用

空的合併運算符

空合併運算符 ?? 會選擇左邊的運算結果(若是它不是 null 的話)或右邊的運算結果(若是它是 null 的話)。

android:text="@{user.displayName ?? user.lastName}"
複製代碼

這在功能上等同於:

android:text="@{user.displayName != null ? user.displayName : user.lastName}"
複製代碼

屬性引用

當一個表達式引用一個類的屬性時,它對字段,setterObservableFields 使用相同的格式。

android:text="@{user.lastName}"
複製代碼

避免空指針異常

生成的數據綁定代碼會自動檢查空值並避免空指針異常。 例如,在表達式 @ {user.name} 中,若是 user 爲 null,則 user.name 將被分配其默認值(null)。 若是引用 user.age,其中age是一個 int,那麼它將默認爲0。

集合

通用的集合:數組,列表,SparseArray ,map,可使用 [] 運算符來方便地訪問。

<data>
    <import type="android.util.SparseArray"/>
    <import type="java.util.Map"/>
    <import type="java.util.List"/>
    <variable name="list" type="List&lt;String&gt;"/>
    <variable name="sparse" type="SparseArray&lt;String&gt;"/>
    <variable name="map" type="Map&lt;String, String&gt;"/>
    <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']}"
複製代碼

資源

使用正常的語法能夠將資源做爲表達式的一部分進行訪問:

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)}"
複製代碼

一些資源須要明確的類型評估:

Type Normal Reference Expression Reference
String[] @array @stringArray
int[] @array @intArray
TypedArray @array @typedArray
Animator @animator @animator
StateListAnimator @animator @stateListAnimator
color int @color @color
ColorStateList @color @colorStateList

Data Objects

任何普通的舊 Java 對象(POJO)均可以用於數據綁定,但修改 POJO 不會致使 UI 更新。 數據綁定的真正威力在於經過給你的數據對象在數據改變時提供通知。 有三種不一樣的數據更改通知機制,Observable objects, observable fields, observable collections.

當這些可觀察的數據對象被綁定到 UI,而且數據對象的屬性改變時,UI 將被自動更新。

Observable Objects

實現 Observable 接口的類將容許綁定單個偵聽器附加到綁定對象,以偵聽該對象上全部屬性的更改。

Observable 接口具備添加和刪除偵聽器的功能,但通知是由開發者決定的。 爲了簡化開發,建立了基類 BaseObservable,以實現偵聽器註冊機制。 數據類實現者仍然負責通知屬性的更改。 這是經過給 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 註解在編譯期間在 BR 類中生成一個條目。 BR 類文件將在模塊包中生成。 若是數據類的基類沒有改變,Observable 接口可使用方便的 PropertyChangeRegistry 來實現,以有效地存儲和通知監聽器。

ObservableFields

建立 Observable 類須要作一點工做,因此想要節省時間或擁有不多屬性的開發人員可使用 ObservableField 及其同胞 ObservableBooleanObservableByteObservableCharObservableShortObservableIntObservableLongObservableFloatObservableDoublObservableParcelableObservableFields 是具備單個字段的獨立的可觀察對象。 原始版本在訪問操做期間避免裝箱和取消裝箱。 要使用,請在數據類中建立一個公共 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();
複製代碼

Observable Collections

一些應用程序使用更多的動態結構來保存數據,觀察集合容許對這些數據對象進行鍵值訪問。當鍵是引用類型(如 String)時,ObservableArrayMap 很是有用。

ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
複製代碼

在佈局文件中,map 經過字符串鍵來訪問:

<data>
    <import type="android.databinding.ObservableMap"/>
    <variable name="user" type="ObservableMap&lt;String, Object&gt;"/>
</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);
複製代碼

在佈局中,列表能夠經過索引來訪問:

<data>
    <import type="android.databinding.ObservableList"/>
    <import type="com.example.my.app.Fields"/>
    <variable name="user" type="ObservableList&lt;Object&gt;"/>
</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"/>
複製代碼

生成綁定類

生成的綁定類將佈局變量與佈局中的視圖連接起來。 如前所述,綁定的名稱和包多是自定義的。 生成的綁定類都擴展了 ViewDataBinding

建立

應該在 inflate 以後當即建立綁定,以確保 View 層次結構不受干擾。 有幾種方法能夠綁定到佈局。 最多見的是在綁定類中使用靜態方法。inflate 方法 inflate View 層次結構,一步到位。 有一個更簡單的版本,只須要一個 LayoutInflater 和一個 ViewGroup

MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);
複製代碼

若是佈局使用不一樣的機制 inflate,它可能會被分開綁定:

MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
複製代碼

有時綁定不能預先知道。 在這種狀況下,可使用 DataBindingUtil 類建立綁定:

ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId, parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);
複製代碼

Views With IDs

將在佈局中爲每一個視圖生成一個公開的 final 字段。 該綁定在視圖層次結構上執行單個傳遞,提取帶有 ID 的視圖。 這個機制能夠比調用多個視圖的 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>
複製代碼

會生成帶有一下字段的綁定類:

public final TextView firstName;
public final TextView lastName;
複製代碼

IDs 不像沒有數據綁定那樣必要,可是仍然有一些狀況下代碼須要訪問視圖。

變量

每一個變量將被賦予訪問器方法。

<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>
複製代碼

會在綁定類中生成 setter 和 getter 方法:

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);
複製代碼

ViewStubs

ViewStub 與普通視圖有點不一樣。 他們從不可見的時候開始,當他們要麼變得可見時,要麼被明確告知 inflate 時,他們經過 inflate 另外一個佈局來取代佈局。

因爲 ViewStub 本質上從視圖層次中消失,因此綁定對象中的視圖也必須消失以容許收集。 由於視圖是 final 的,因此 ViewStubProxy 對象代替了ViewStub,當 ViewStub 存在時,開發人員能夠訪問 ViewStub,而且在 ViewStub被 inflate 時也能夠訪問被 inflate 的視圖。

當 inflate 另外一個佈局時,必須爲新的佈局創建綁定。所以,ViewStubProxy 必須偵聽 ViewStubViewStub.OnInflateListener 並在此時創建綁定。因爲只有一個能夠存在,ViewStubProxy 容許開發者在創建綁定以後設置一個 OnInflateListener 對象。

高級綁定

動態變量

有時,特定的綁定類將不被知道。 例如,針對任意佈局的 RecyclerView.Adapter 將不知道具體的綁定類。 它仍然必須在 onBindViewHolder(VH,int) 期間分配綁定值。

在這個例子中,RecyclerView 綁定的全部佈局都有一個 item 變量。BindingHolder 有一個返回 ViewDataBinding 基類的 getBinding 方法。

public void onBindViewHolder(BindingHolder holder, int position) {
   final T item = mItems.get(position);
   holder.getBinding().setVariable(BR.item, item);
   holder.getBinding().executePendingBindings();
}
複製代碼

當即綁定

當變量或 observable 變化時,綁定將被安排在下一幀以前改變。但有時候,綁定必須當即執行。要強制執行,請使用 executePendingBindings() 方法。

後臺線程

只要不是集合,就能夠在後臺線程中更改數據模型。數據綁定將在評估時本地化每一個變量/字段,以免任何併發問題。


屬性設置

每當綁定值發生變化時,生成的綁定類必須使用綁定表達式在視圖上調用setter方法。 數據綁定框架能夠自定義調用哪一個方法來設置值。

自動的設置器

對於一個屬性,數據綁定將試圖找到設置屬性的方法。屬性的命名空間並不重要,只有屬性名稱自己才重要。例如,與 TextView 的屬性 android:text 相關聯的表達式將查找 setText(String)。 若是表達式返回 int,那麼數據綁定將搜索一個 setText(int) 方法。請注意讓表達式返回正確的類型,若是須要的話就進行轉換。即便給定名稱不存在任何屬性,數據綁定也能夠工做。 而後,您可使用數據綁定輕鬆地爲任何 setter 建立屬性。 例如,support 庫中的 DrawerLayout 沒有任何屬性,可是有不少 setter。 您可使用自動設置器來使用其中的一個。

<android.support.v4.widget.DrawerLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:scrimColor="@{@color/scrim}"
    app:drawerListener="@{fragment.drawerListener}"/>
複製代碼

重命名設置器

一些屬性的設置器會與名稱不匹配。 對於這些方法,一個屬性可能經過 BindingMethods 註解與設置器關聯。 這必須與一個類相關聯,每一個重命名的方法一個包含一個 BindingMethod 註解。例如,android:tint 屬性確實與 setImageTintList(ColorStateList) 關聯,而不是 setTint

@BindingMethods({
       @BindingMethod(type = "android.widget.ImageView",
                      attribute = "android:tint",
                      method = "setImageTintList"),
})
複製代碼

開發人員不太可能須要重命名設置器, 安卓框架已經爲這些屬性實現了。

自定義設置器

一些屬性須要自定義綁定邏輯。 例如,android:paddingLeft 屬性沒有關聯的設置器。 相反,setPadding(eft, top, right, bottom) 存在。 使用 BindingAdapter 註釋的靜態綁定適配器方法容許開發人員自定義如何調用屬性的設置器。

安卓屬性已經建立了 BindingAdapters。 例如,這裏是 paddingLeft

@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
   view.setPadding(padding,
                   view.getPaddingTop(),
                   view.getPaddingRight(),
                   view.getPaddingBottom());
}
複製代碼

綁定適配器對其餘類型的自定義很是有用。 例如,一個自定義的加載器能夠被調用脫機線程來加載一個圖像。當發生衝突時,開發人員建立的綁定適配器將覆蓋數據綁定默認適配器。您也可讓適配器接收多個參數。

@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}"/>
複製代碼

若是 imageUrl 和 error 都用於 ImageView 且 imageUrl 是字符串,而且 error 是 drawable,則將調用此適配器。 自定義名稱空間在匹配過程當中被忽略。 也能夠爲 android 命名空間編寫適配器。 綁定適配器方法能夠選擇在其處理程序中使用舊值。 採用新舊值的方法,應該把屬性的全部舊的值放在第一位,而後是新的值:

@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);
        }
    }
}
複製代碼

當一個監聽器有多個方法時,它必須被分紅多個監聽器。例如,View.OnAttachStateChangeListener 有兩個方法:onViewAttachedToWindow()onViewDetachedFromWindow()。而後咱們必須建立兩個接口來區分它們的屬性和處理器。

@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewDetachedFromWindow {
    void onViewDetachedFromWindow(View v);
}

@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewAttachedToWindow {
    void onViewAttachedToWindow(View v);
}
複製代碼

由於更改一個偵聽器也會影響另外一個偵聽器,因此咱們必須有三個不一樣的綁定適配器,一個用於每一個屬性,另外一個用於兩個,它們都應該被設置。

@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);
        }
    }
}
複製代碼

上面的例子比正常狀況稍微複雜一點,由於視圖對偵聽器使添加和刪除,而不是對 View.OnAttachStateChangeListener 使用set方法。 android.databinding.adapters.ListenerUtil 類有助於跟蹤之前的監聽器,以便它們能夠在綁定適配器中被移除。經過使用 @TargetApi(VERSION_CODES.HONEYCOMB_MR1) 註解接口 OnViewDetachedFromWindowOnViewAttachedToWindow,數據綁定代碼生成器知道只應在 API 12 或以上的設備上調用 addOnAttachStateChangeListener(View.OnAttachStateChangeListener) 來運行運行偵聽器。


轉換器

對象轉換

從綁定表達式返回一個對象時,將從自動,重命名和自定義的設置器中選擇一個設置器。 該對象將被轉換爲所選設置器的參數類型。 這對於那些使用 ObservableMaps 來保存數據的開發者來講是很方便的。例如:

<TextView
   android:text='@{userMap["lastName"]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
複製代碼

userMap 返回一個對象,該對象將被自動轉換爲在 setText(CharSequence) 中找到的參數類型。 當參數類型可能混淆時,開發者須要在表達式中輸入。

自定義轉換

有時轉換應該在特定類型之間自動進行。 例如,設置 background 時:

<View
   android:background="@{isError ? @color/red : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
複製代碼

在這裏,背景須要一個 Drawable,可是顏色是一個整數。每當一個 Drawable 被判斷該返回一個整數時,該整形應該被轉換成一個 ColorDrawable。 這個轉換是經過一個帶有 BindingConversion 註解的靜態方法完成的:

@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
   return new ColorDrawable(color);
}
複製代碼

請注意,轉換隻發生在設置器級別,因此不容許混合類型,以下所示:

<View
   android:background="@{isError ? @drawable/error : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
複製代碼

Android Studio 支持數據綁定

Android Studio 支持數據綁定代碼的許多代碼編輯功能。例如,它支持數據綁定表達式的如下功能:

  • 語法高亮
  • 表達式語法錯誤的提示
  • XML代碼完成
  • 包括導航(如導航到聲明)和快速文檔的參考

注意:若是沒有錯誤,則數組和通常類型(如 Observable 類)可能會顯示錯誤。

預覽窗格顯示數據綁定表達式的默認值(若是提供的話)。在如下示例摘錄佈局XML文件中的元素時,預覽窗格將在 TextView 中顯示 PLACEHOLDER 默認文本值。

<TextView android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:text="@{user.firstName, default=PLACEHOLDER}"/>
複製代碼

若是須要在項目設計階段顯示默認值,則還可使用工具屬性而不是默認表達式值,如 Design Time Layout Attributes 中所述。


原文地址:developer.android.google.cn/topic/libra…

相關文章
相關標籤/搜索