Data Binding Library

導(dǎo)航:

本文檔解釋了如何使用數(shù)據(jù)綁定庫來編寫聲明式布局,并盡量減少綁定應(yīng)用程序邏輯和布局所需的膠合代碼。

數(shù)據(jù)綁定庫提供了靈活性和廣泛的兼容性 - 這是一個支持庫,所以你可以在Android 2.1(API級別7+)的所有Android平臺上使用它。

要使用數(shù)據(jù)綁定,Android Plugin for Gradle 1.5.0-alpha1 或更高版本是必需的。了解如何更新Android Plugin for Gradle

1.配置環(huán)境

要開始使用數(shù)據(jù)綁定,請從Android SDK管理器的支持庫中下載庫。

要配置應(yīng)用程序以使用數(shù)據(jù)綁定,請將dataBinding 元素添加到應(yīng)用程序模塊中的build.gradle文件中。

使用下面的代碼片段來配置數(shù)據(jù)綁定:

android {
    ....
    dataBinding {
        enabled = true
    }
}

如果您的應(yīng)用程序模塊依賴于使用數(shù)據(jù)綁定的庫,則您的應(yīng)用程序模塊也必須在其build.gradle 配置數(shù)據(jù)綁定。

另外,請確保您使用的是Android Studio的兼容版本。Android Studio 1.3及更高版本支持?jǐn)?shù)據(jù)綁定,如Android Studio支持?jǐn)?shù)據(jù)綁定中所述

2.數(shù)據(jù)綁定編譯器V2

Android Gradle插件3.1.0 Canary 6附帶一個可選的新編譯器。要開始使用它,請更新您的gradle.properties文件以包含以下行:

android.databinding.enableV2=true

在編譯器v2中:

  • ViewBinding類是在java編譯器之前由Android Gradle Plugin生成的。這可以避免由于不相關(guān)的原因?qū)е耲ava編譯失敗而導(dǎo)致太多的錯誤肯定錯誤。
  • 在V1中,編譯應(yīng)用程序時會重新生成庫的綁定類(以共享生成的代碼并訪問最終的“BR”和“R”文件)。在V2中,庫保持其生成的綁定類以及映射器信息,這為多模塊項目顯著提高了數(shù)據(jù)綁定性能。

請注意,這個新的編譯器是向后不兼容的,所以用v1編譯的庫不能被v2使用,反之亦然。

V2還會刪除一些很少使用的功能來允許這些更改:

  • 在V1中,一個應(yīng)用程序能夠提供綁定適配器,可以覆蓋依賴項中的適配器。在V2中,它只對自己的模塊/應(yīng)用程序及其依賴項中的代碼生效。
  • 以前,如果一個布局文件在兩個或多個不同的資源配置中包含一個 View具有相同id但不同類的數(shù)據(jù),則數(shù)據(jù)綁定將查找最常見的父類。View當(dāng)配置之間的類型不匹配時,它將始終默認(rèn)為。
  • 在V2中,不同的模塊不能在清單中使用相同的包名稱,因為數(shù)據(jù)綁定將使用該包名來生成綁定映射器類。
3.數(shù)據(jù)綁定布局文件

3.1編寫你的第一套數(shù)據(jù)綁定表達(dá)式

數(shù)據(jù)綁定布局文件稍有不同,layout作為根標(biāo)簽,后跟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>

data標(biāo)簽中的user變量,描述了可以在布局中使用的屬性。

<variable name="user" type="com.example.User"/>

布局中的使用@{}語法表達(dá)式寫入屬性。在這里,TextView的文本被設(shè)置為用戶的firstName屬性:

<TextView android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="@{user.firstName}"/>
Data Object
3.2 數(shù)據(jù)對象

現(xiàn)在讓我們假設(shè)你有一個普通的Java對象(PO??JO)用戶:

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ù)永遠(yuǎn)不會改變。在應(yīng)用程序中通常會讀取一次數(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 android:text屬性的表達(dá)式將訪問前一類中的firstName字段和后一類中的getFirstName()方法?;蛘撸绻?code>firstName()方法存在,也將被解析。

3.3 綁定數(shù)據(jù)

默認(rèn)情況下,將根據(jù)布局文件的名稱生成一個Binding類,將其轉(zhuǎn)換為Pascal格式并將Binding后綴添加到該文件中。上面的布局文件是main_activity.xml這樣的生成類是MainActivityBinding。這個類將布局屬性(例如user變量)的所有綁定保存到布局的視圖中,并知道如何為綁定表達(dá)式賦值。創(chuàng)建綁定的最簡單方法是在inflating時進(jìn)行:

@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);
}

你完成了!運行應(yīng)用程序,你會看到用戶界面中的測試user。或者,您可以通過以下方式獲取視圖:

MainActivityBinding binding = 
MainActivityBinding.inflate(getLayoutInflater());

如果您在ListView或RecyclerView適配器內(nèi)使用數(shù)據(jù)綁定項目,則可能更愿意使用:

ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
Event Handling
3.4 事件處理

數(shù)據(jù)綁定允許您編寫表達(dá)式來處理從視圖中分派的事件(例如onClick)。除少數(shù)例外,事件屬性名稱由偵聽器方法的名稱來管理。View.OnLongClickListener 有一個方法 onLongClick(),所以這個事件的屬性是android:onLongClick。處理事件有兩種方法。

  • 方法引用:在你的表達(dá)式中,你可以引用符合偵聽器方法簽名的方法。當(dāng)表達(dá)式評估為方法引用時,數(shù)據(jù)綁定將方法引用和所有者對象包裝在偵聽器中,并將該偵聽器設(shè)置在目標(biāo)視圖上。如果表達(dá)式求值為null,則數(shù)據(jù)綁定不會創(chuàng)建偵聽器,而是設(shè)置空偵聽器。
  • 監(jiān)聽器綁定:這些是在事件發(fā)生時被評估的lambda表達(dá)式。數(shù)據(jù)綁定總是創(chuàng)建一個監(jiān)聽器,它在視圖上設(shè)置。事件發(fā)送時,監(jiān)聽器評估lambda表達(dá)式。
3.4.1方法引用

事件可以直接綁定到處理方法,類似于 android:onClick可以分配給Activity中的方法。與View#onClick屬性相比,一個主要的優(yōu)點是表達(dá)式在編譯時被處理,所以如果方法不存在或者它的簽名不正確,你會收到一個編譯時錯誤。

方法引用和監(jiān)聽器綁定的主要區(qū)別在于實際的監(jiān)聽器實現(xiàn)是在綁定數(shù)據(jù)時創(chuàng)建的,而不是在事件觸發(fā)時創(chuàng)建的。如果您喜歡在事件發(fā)生時評估表達(dá)式,則應(yīng)該使用監(jiān)聽器綁定。

要將事件分配給其處理程序,請使用常規(guī)綁定表達(dá)式,其值是要調(diào)用的方法名稱。例如,如果你的數(shù)據(jù)對象有兩個方法:

public class MyHandlers {
    public void onClickFriend(View view) { ... }
}

綁定表達(dá)式可以為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.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>

請注意,表達(dá)式中方法的簽名必須與Listener對象中方法的簽名完全匹配

3.4.2監(jiān)聽器綁定

監(jiān)聽器綁定是事件發(fā)生時運行的綁定表達(dá)式。它們類似于方法引用,但是它們允許您運行任意的數(shù)據(jù)綁定表達(dá)式。此功能適用于Gradle 2.0版及更高版本的Android Gradle插件。

在方法引用中,方法的參數(shù)必須與事件偵聽器的參數(shù)匹配。在監(jiān)聽器綁定中,只有你的返回值必須與監(jiān)聽器的期望返回值相匹配(除非它預(yù)期為void)。例如,您可以有一個具有以下方法的演示者類:

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)聽器僅允許只允許由lambda表達(dá)式作為根元素的表達(dá)式。在表達(dá)式中使用回調(diào)函數(shù)時,數(shù)據(jù)綁定會自動為事件創(chuàng)建必要的偵聽器和注冊表。當(dāng)視圖觸發(fā)事件時,數(shù)據(jù)綁定將評估給定的表達(dá)式。就像在常規(guī)的綁定表達(dá)式中一樣,當(dāng)這些監(jiān)聽器表達(dá)式被評估的時候,你仍然可以獲得數(shù)據(jù)綁定的null和線程安全性。

請注意,在上面的例子中,我們沒有定義view傳入?yún)?shù)onClick(android.view.View)。監(jiān)聽器綁定為監(jiān)聽器參數(shù)提供了兩個選擇:您可以忽略該方法的所有參數(shù)或?qū)⑵淙棵?。如果您想要命名參?shù),則可以在表達(dá)式中使用它們。例如,上面的表達(dá)式可以寫成:

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

或者如果你想使用表達(dá)式中的參數(shù),它可以如下工作:

public class Presenter {
    public void onSaveClick(View view, Task task){}
}

---

android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"


您可以使用多于一個參數(shù)的lambda表達(dá)式:

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的值,則表達(dá)式必須返回相同類型的值。例如,如果要監(jiān)聽長按事件,則應(yīng)該返回表達(dá)式boolean

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

如果由于是null對象而無法評估表達(dá)式,Data Binding將返回該類型的默認(rèn)Java值。例如,null返回引用類型,0返回 intfalse返回 boolean等。

如果您需要使用謂詞(例如三元)表達(dá)式,則可以將 void用作符號。

android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"
避免復(fù)雜的監(jiān)聽

監(jiān)聽器表達(dá)式非常強(qiáng)大,可以讓你的代碼非常容易閱讀。另一方面,包含復(fù)雜表達(dá)式的監(jiān)聽會使您的布局難以閱讀和維護(hù)。這些表達(dá)式應(yīng)該像從UI中傳遞可用數(shù)據(jù)到回調(diào)方法一樣簡單。您應(yīng)該在您從偵聽器表達(dá)式調(diào)用的回調(diào)方法內(nèi)實現(xiàn)任何業(yè)務(wù)邏輯。

存在一些專門的單擊事件處理程序,它們需要一個屬性, android:onClick以避免沖突。已經(jīng)創(chuàng)建了以下屬性以避免這種沖突:

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

4.布局細(xì)節(jié)


4.1 import

data元素中可以使用零個或多個import元素,這些就像在Java中一樣可以輕松地引用布局文件中的類。

<data>
    <import type="android.view.View"/>
</data>

現(xiàn)在,可以在你的綁定表達(dá)式中使用視圖:

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

當(dāng)有類名沖突時,其中一個類可能會被重命名為“alias:”

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

現(xiàn)在,Vista可能會以com.example.real.estate.View,View可能以android.view.View在布局文件內(nèi)引用。導(dǎo)入的類型可以用作變量和表達(dá)式中的類型引用:

<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尚未處理導(dǎo)入,因此導(dǎo)入變量的自動填充可能無法在您的IDE中工作。您的應(yīng)用程序仍然可以正常編譯,您可以通過在變量定義中使用完全限定的名稱來解決IDE問題。

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

當(dāng)在表達(dá)式中引用靜態(tài)字段和方法時,也可以使用導(dǎo)入的類型:

<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.*自動導(dǎo)入。

4.2 Variables

data元素內(nèi)可以使用任意數(shù)量的variable元素,每個variable元素描述可以在布局上設(shè)置的屬性,以用于布局文件中的綁定表達(dá)式。

<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>

變量類型在編譯時被檢查,所以如果一個變量實現(xiàn)Observable或者是一個observable collection,那么這個類型應(yīng)該被反映出來。如果變量是不實現(xiàn)Observable接口的基類或接口,將不會觀察變量!

當(dāng)不同的配置文件(例如橫向或縱向)有不同的布局文件時,變量將被合并。這些布局文件之間不得存在沖突的變量定義。
生成的綁定類將為每個描述的變量設(shè)置一個setter和getter。變量將采用默認(rèn)的Java值,直到調(diào)用者被調(diào)用 - null是引用類型,0int,falseboolean等。

根據(jù)需要生成一個名為context的特殊變量用于綁定表達(dá)式。context是視圖的getContext()得到的Context。context變量將被具有該名稱的顯式變量聲明覆蓋。

4.3自定義綁定類名稱

默認(rèn)情況下,根據(jù)布局文件的名稱生成一個Binding類,以大寫字母開頭,刪除下劃線(_)并大寫下面的字母,然后后綴“Binding”。這個類將被放置在模塊包下的數(shù)據(jù)綁定包中。例如,布局文件contact_item.xml將生成 ContactItemBinding。如果模塊包是 com.example.my.app,那么它將被放入com.example.my.app.databinding。

綁定類可以通過調(diào)整class元素的屬性來重命名或放置data在不同的包中。

<data class="ContactItem">
    ...
</data>

這將在模塊包中的數(shù)據(jù)綁定包中生成綁定類ContactItem。如果該類應(yīng)該在模塊包中的其他包中生成,則可以用.作為前綴:

<data class=".ContactItem">
    ...
</data>

在這種情況下,ContactItem直接在模塊包中生成。如果提供完整的軟件包,則可以在任何軟件包中:

<data class="com.example.ContactItem">
    ...
</data>
4.4 Includes

通過使用應(yīng)用程序名稱空間和屬性的變量名,變量可以被傳遞到包含布局的包含綁定中:

<?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變量。

數(shù)據(jù)綁定不支持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>
4.5 1. 表達(dá)式語言
共同特征

表達(dá)式語言看起來很像Java表達(dá)式。這些是一樣的:

  • 數(shù)學(xué)的: + - / * %
  • 字符串連接: +
  • 邏輯: && ||
  • 二進(jìn)制: & | ^
  • 一元: + - ! ~
  • 轉(zhuǎn)移: >> >>> <<
  • 對照: == > < >= <=
  • instanceof
  • 分組: ()
  • 文字 - 字符,字符串,數(shù)字, null
  • Cast
  • 方法調(diào)用
  • 字段訪問
  • 數(shù)組訪問 []
  • 三元操作符 ?:

例子:

android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}
不支持的操作

您可以在Java中使用的表達(dá)式語法中缺少一些操作。

  • this
  • super
  • new
  • 明確的泛型調(diào)用
空合并運算符

null合并運算符(??)選擇左操作數(shù)(如果不是null)或右(如果為空)。

android:text="@{user.displayName ?? user.lastName}"

這在功能上等同于:

android:text="@{user.displayName != null ? user.displayName : user.lastName}"

屬性引用

第一個已經(jīng)在上面的編寫第一個數(shù)據(jù)綁定表達(dá)式中討論過了:簡短形式的JavaBean引用。當(dāng)一個表達(dá)式引用一個類的屬性時,它對字段,獲取器和ObservableFields使用相同的格式。

android:text="@{user.lastName}"
避免空指針異常

生成的數(shù)據(jù)綁定代碼自動檢查空值并避免空指針異常。例如,在 @{user.name}表達(dá)式中如果user為null, user.name將被賦予其默認(rèn)值(null)。如果你是引用user.age,年齡是一個int,那么它將默認(rèn)為0。

集合

公共集合:數(shù)組,列表,稀疏列表和地圖,為了方便可以使用[]操作符。

<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]}"
字符串文字

在屬性值周圍使用單引號時,在表達(dá)式中使用雙引號很容易:

android:text='@{map["firstName"]}'

也可以使用雙引號來包圍屬性值。這樣做時,字符串文字應(yīng)該使用'或者反引號( ` )。

android:text="@{map[`firstName`}"
android:text="@{map['firstName']}"
資源

使用正常語法可以將資源作為表達(dá)式的一部分進(jìn)行訪問:

android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"

格式字符串和復(fù)數(shù)可以通過提供參數(shù)來評估:

android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"

當(dāng)一個復(fù)數(shù)有多個參數(shù)時,所有參數(shù)都應(yīng)該傳遞:

  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
5.數(shù)據(jù)對象

任何普通的舊Java對象(PO??JO)都可以用于數(shù)據(jù)綁定,但修改POJO不會導(dǎo)致UI更新。數(shù)據(jù)綁定的真正威力可以通過給你的數(shù)據(jù)對象提供在數(shù)據(jù)改變時通知的能力。有三種不同的數(shù)據(jù)更改通知機(jī)制: 可觀察對象可觀察字段可觀察集合。
當(dāng)這些可觀察的數(shù)據(jù)對象之一被綁定到UI并且數(shù)據(jù)對象的屬性改變時,UI將被自動更新。

5.1可觀察的對象

實現(xiàn)Observable接口的類將允許綁定將單個偵聽器附加到綁定對象,以偵聽該對象上所有屬性的更改。

Observable接口具有添加和刪除偵聽器的機(jī)制,但通知由開發(fā)人員決定。為了簡化開發(fā),創(chuàng)建了一個BaseObservable基類來實現(xiàn)監(jiān)聽器注冊機(jī)制。數(shù)據(jù)類實現(xiàn)者仍然負(fù)責(zé)通知屬性何時更改,通過分配一個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 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類文件將在模塊包中生成。如果數(shù)據(jù)類的基類不能改變,那么Observable將會被實現(xiàn),通過使用PropertyChangeRegistry去存儲和有效地通知偵聽器。

5.2 可觀察字段

Observable類大有內(nèi)涵,所以開發(fā)者想要節(jié)省時間和添加有幾個屬性,可以使用ObservableField和它的兄弟姐妹
ObservableBoolean,ObservableByteObservableChar,ObservableShort,ObservableInt,ObservableLongObservableFloat,ObservableDouble,和ObservableParcelable。

ObservableFields是具有單個字段的自包含可觀察對象。原始版本在訪問操作期間避免裝箱和取消裝箱。要使用,請在數(shù)據(jù)類中創(chuàng)建一個公共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 可觀察的集合

一些應(yīng)用程序使用更多的動態(tài)結(jié)構(gòu)來保存數(shù)據(jù),可觀察集合允許對這些數(shù)據(jù)對象進(jìn)行鍵控訪問。當(dāng)鍵是引用類型(如String)時ObservableArrayMap非常有用。

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

在布局中,可以通過String鍵訪問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"/>

當(dāng)鍵是一個整數(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&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"/>
6.生成的綁定

生成的綁定類將布局變量與布局中的視圖鏈接起來。如前所述,綁定的名稱和包可能是 自定義的。生成的綁定類全部擴(kuò)展ViewDataBinding。

6.1創(chuàng)建

應(yīng)該在inflation之后立即創(chuàng)建綁定,以確保在綁定到布局中帶有表達(dá)式的視圖之前,View層次結(jié)構(gòu)不受干擾。有幾種方法可以綁定到布局。最常見的是在Binding類中使用靜態(tài)方法。inflate方法inflate了View層次結(jié)構(gòu),并將其一步綁定。有一個簡單的版本,只需要一個LayoutInflaterViewGroup

MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);

如果布局使用不同的inflate機(jī)制,它可能會被分開綁定:

MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);

有時綁定不能預(yù)先知道。在這種情況下,綁定可以使用DataBindingUtil類創(chuàng)建:

ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
    parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);
6.2帶有ID的視圖

每個帶有ID的視圖將在布局中生成一個對應(yīng)的公開fianl字段。該綁定在View層次結(jié)構(gòu)上執(zhí)行單個傳遞,提取帶有ID的視圖。這個機(jī)制可以比調(diào)用多個視圖的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;

ID在數(shù)據(jù)綁定時不是必須的,但是仍然有一些情況下代碼仍然需要訪問視圖。

6.3變量

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

<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和getters:

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);
6.4ViewStubs

ViewStub與正常view有些不同。他們從不可見的時候開始,當(dāng)他們要么變得可見時,或者被明確地告知inflate時,他們通過inflate另一種布局來取代布局。

由于ViewStub本質(zhì)上從View層次消失,綁定對象中的View也必須消失以允許收集。因為view是最終的,所以一個ViewStubProxy對象代替了這個ViewStub視圖,當(dāng)開發(fā)者存在的時候,它允許開發(fā)人員訪問ViewStub,并且在ViewStubinflate時也可以訪問inflate的View層次結(jié)構(gòu)。

當(dāng)inflate另一個布局時,必須為新的布局建立綁定。因此,ViewStubProxy一定監(jiān)聽ViewStubViewStub.OnInflateListener,同時建立綁定。由于只有一個可以存在,所以ViewStubProxy允許開發(fā)者設(shè)置一個OnInflateListener,在建立綁定之后它將被調(diào)用

6.5高級綁定
動態(tài)變量

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

在這個例子中,RecyclerView綁定的所有布局都有一個“item”變量。所述的BindingHolder具有getBinding方法返回基礎(chǔ)的ViewDataBinding。

public void onBindViewHolder(BindingHolder holder, int position) {
   final T item = mItems.get(position);
   holder.getBinding().setVariable(BR.item, item);
   holder.getBinding().executePendingBindings();
}
6.6 立即綁定

當(dāng)變量或可觀察對象變化時,綁定將被安排在下一幀之前改變。但有時候,綁定必須立即執(zhí)行。要強(qiáng)制執(zhí)行,請使用該executePendingBindings()方法。

6.7后臺線程

只要不是集合,就可以在后臺線程中更改數(shù)據(jù)模型。數(shù)據(jù)綁定將在評估時本地化每個變量/字段,以避免任何并發(fā)問題。

7. 屬性Setters

每當(dāng)綁定值發(fā)生變化時,生成的綁定類必須使用綁定表達(dá)式在視圖上調(diào)用setter方法。數(shù)據(jù)綁定框架可以自定義調(diào)用哪個方法來設(shè)置值。

7.1 自動 Setters

對于一個屬性,數(shù)據(jù)綁定試圖找到方法setAttribute。屬性的命名空間并不重要,只有屬性名稱本身才是重點。

例如,與TextView屬性關(guān)聯(lián)的表達(dá)式 android:text將查找setText(String)。如果表達(dá)式返回一個int,那么數(shù)據(jù)綁定將搜索一個setText(int)方法。請注意讓表達(dá)式返回正確的類型,如果需要的話就進(jìn)行轉(zhuǎn)換。請注意,即使給定名稱不存在任何屬性,數(shù)據(jù)綁定也可以工作。然后,您可以使用數(shù)據(jù)綁定輕松地為任何‘創(chuàng)建’的屬性進(jìn)行setter。例如,support DrawerLayout沒有任何屬性,但是有很多setter。您可以使用自動設(shè)置器來使用其中的一個。

<android.support.v4.widget.DrawerLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:scrimColor="@{@color/scrim}"
    app:drawerListener="@{fragment.drawerListener}"/>
7.2重命名Setters

一些擁有setters方法的屬性與名稱不符合。對于這些方法,屬性可能通過BindingMethods注釋與setter相關(guān)聯(lián)。這必須與一個類相關(guān)聯(lián),并包含BindingMethod注釋,每個重命名的方法一個。例如,android:tint屬性確實與setImageTintList(ColorStateList)關(guān)聯(lián),而不是 setTint

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

開發(fā)人員不太可能需要重命名setter;android框架的屬性已經(jīng)實現(xiàn)了。

7.3自定義setters

一些屬性需要自定義綁定邏輯。例如,android:paddingLeft 屬性沒有關(guān)聯(lián)的setter。相反,setPadding(left, top, right, bottom)存在。帶BindingAdapter注釋的靜態(tài)綁定適配器方法,允許開發(fā)人員定制如何調(diào)用屬性的setter。

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());
}

綁定適配器對其他類型的自定義非常有用。例如,一個自定義的加載器可以被調(diào)用脫機(jī)線程來加載一個圖像。
當(dāng)發(fā)生沖突時,開發(fā)人員創(chuàng)建的綁定適配器將覆蓋數(shù)據(jù)綁定默認(rèn)適配器。

您也可以讓適配器接收多個參數(shù)。

@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}"/>

如果imageUrlerror都用于ImageView且imageUrl是字符串,并且errordrawable,則將調(diào)用此適配器。

  • 自定義名稱空間在匹配過程中被忽略。
  • 您也可以為android命名空間編寫適配器。

綁定適配器方法可以選擇性的在其處理程序中使用舊值。采用新舊價值的方法,首先應(yīng)該擁有屬性的舊值,其次是新的值:

@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);
        }
    }
}

當(dāng)一個監(jiān)聽器有多個方法時,它必須被分成多個監(jiān)聽器。比如View.OnAttachStateChangeListener有兩種方法:onViewAttachedToWindow()onViewDetachedFromWindow()。然后我們必須創(chuàng)建兩個接口來區(qū)分它們的屬性和處理程序。

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

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

因為更改一個偵聽器也會影響另一個偵聽器,所以我們必須有三個不同的綁定適配器,一個用于每個屬性,另一個用于兩個,如果它們都被設(shè)置。

@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);
        }
    }
}

上面的例子比正常情況稍微復(fù)雜,因為View使用add和remove來代替View.OnAttachStateChangeListener的set方法android.databinding.adapters.ListenerUtil類可以幫助跟蹤以前的監(jiān)聽,讓他們可以在綁定Adaper中被刪除的。

通過用@TargetApi(VERSION_CODES.HONEYCOMB_MR1)注釋OnViewDetachedFromWindowOnViewAttachedToWindow接口,數(shù)據(jù)綁定代碼生成器知道監(jiān)聽器只在Honeycomb MR1和新設(shè)備生成;同樣的情況在addOnAttachStateChangeListener(View.OnAttachStateChangeListener)上發(fā)生。

8. 轉(zhuǎn)換器

8.1對象轉(zhuǎn)換

從綁定表達(dá)式返回一個對象時,將從自動,重命名和自定義setter中選擇一個setter。該對象將被轉(zhuǎn)換為所選setter的參數(shù)類型。

這對于那些使用ObservableMaps來保存數(shù)據(jù)的人來說非常方便。例如:

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

將返回一個userMap對象,并且將對象自動轉(zhuǎn)換為setText(CharSequence)中的參數(shù)類型。當(dāng)參數(shù)類型可能混淆時,開發(fā)人員需要在表達(dá)式中輸入。

8.2 自定義轉(zhuǎn)換

有時轉(zhuǎn)換應(yīng)該在特定類型之間自動進(jìn)行。例如,設(shè)置背景時:

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

在這里,背景需要一個Drawable,但顏色是一個integer。不管期望 Drawable或是返回一個integer,int應(yīng)該被轉(zhuǎn)換成一個ColorDrawable。這個轉(zhuǎn)換是通過一個帶有BindingConversion注解的靜態(tài)方法完成的:

@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
   return new ColorDrawable(color);
}

請注意,轉(zhuǎn)換只發(fā)生在setter級別,所以不允許混合類型如下所示:

<View
   android:background="@{isError ? @drawable/error : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
9. Android Studio支持?jǐn)?shù)據(jù)綁定

Android Studio支持?jǐn)?shù)據(jù)綁定代碼的許多代碼編輯功能。例如,它支持?jǐn)?shù)據(jù)綁定表達(dá)式的以下功能:

  • 語法高亮顯示
  • 表達(dá)式語言語法錯誤的標(biāo)記
  • XML代碼完成
  • 參考文獻(xiàn),包括導(dǎo)航(如導(dǎo)航到聲明)和 快速文檔的參考

注意:數(shù)組和 泛型類型(如Observable類)可能會在沒有錯誤時,顯示錯誤。

預(yù)覽窗格顯示數(shù)據(jù)綁定表達(dá)式的默認(rèn)值(如果提供)。在下面的示例摘錄了布局XML文件中的元素后,“預(yù)覽”窗口將在TextView中顯示PLACEHOLDER默認(rèn)的文本值。

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

如果需要在項目設(shè)計階段顯示默認(rèn)值,則還可以使用工具屬性而不是默認(rèn)表達(dá)式值,如 Design Time Layout Attributes中所述。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,725評論 25 709
  • 2017.8.15 初次添加2017.12.20 更新BindingMethod注解 如果你是第一次使用強(qiáng)烈推薦你...
    談小龍閱讀 591評論 0 1
  • 1.前言 不知道是否還記得前段時間講得Android三種主流開發(fā)框架,要是忘了,可以回顧一下。當(dāng)時提到了MVVM框...
    lanceJin閱讀 964評論 0 7
  • 星期一終于到了。我期盼已久的旅行就要開始了!我特別高興特別興奮。我們一行五個人,有我的小閨蜜和我們一家三口,還有她...
    劉玥妙閱讀 529評論 0 2
  • 博物館回憶錄 一位中年婦女,笑著打趣道:“這可以寫進(jìn)我的書里”,順著她的目光有一位年輕女孩,但看起來很邋遢,穿著土...
    戴眼鏡的灰熊閱讀 194評論 0 0

友情鏈接更多精彩內(nèi)容