Android Data Binding(數(shù)據(jù)綁定)用戶指南(譯)

轉(zhuǎn)載

Android Data Binding(數(shù)據(jù)綁定)用戶指南

[TOC]

1)介紹

本文檔解釋了如何使用數(shù)據(jù)綁定庫來編寫聲明式布局,并且用最少的代碼來綁定你的app邏輯和layouts文件亲雪。

Data Binding庫既具有靈活性勇凭,又具有廣泛的兼容性——它是一個support持庫,因此你可以在所有的Android平臺最低能到Android 2.1(API等級7+)上使用它义辕。

為了使用數(shù)據(jù)綁定虾标,需要Android插件1.5.0 - alpha1或更高版本。

2)構(gòu)建環(huán)境

要開始使用Data Binding灌砖,首先需要在Android SDK Manager的支持庫里下載該庫璧函。

你的app要使用Data Binding,需要添加Data Binding到gradle構(gòu)建文件里基显,如下:

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

Data Binding插件將會在你的項目內(nèi)添加必需提供的以及編譯配置依賴蘸吓。

請確保您使用的是Android Studio的兼容版本。Android Studio的Data Binding插件需要Android Studio 1.3.0 或 更高版本撩幽。

3) Data Binding Layout文件

a) Data Binding表達(dá)式

Data Binding layout文件有點(diǎn)不同的是:起始根標(biāo)簽是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內(nèi)描述了一個名為user的變量屬性宪萄,使其可以在這個layout中使用:

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

在layout的屬性表達(dá)式寫作@{},下面是一個TextView的text設(shè)置為user的firstName屬性:

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

b)Data對象

假設(shè)你有一個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;
   }
}

這個類型的對象擁有從不改變的數(shù)據(jù)榨惰。在app中它是常見的拜英,可以讀取一次并且之后從不改變。當(dāng)然也可以使用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屬性的表達(dá)式@{user.firstName}將訪問前者POJO對象中的firstName和后者JavaBeans對象中的getFirstName()方法虫给。

c)Binding數(shù)據(jù)

默認(rèn)情況下,一個Binding類會基于layout文件的名稱而產(chǎn)生排监,將其轉(zhuǎn)換為Pascal case(譯注:首字母大寫的命名規(guī)范)并且添加“Binding”后綴狰右。上述的layout文件是main_activity.xml,因此生成的類名是MainActivityBinding舆床。此類包含從layout屬性到layout的Views中所有的bindings(例如user變量),并且它還知道如何給Binding表達(dá)式分配數(shù)值嫁佳。創(chuàng)建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);
}

就是這樣挨队,運(yùn)行app后,你將會看到Test User蒿往∈⒖眩或者你可以通過如下獲取View:

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

如果你在ListView或者RecyclerView adapter使用Data Binding時,你可能會使用:

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

d)事件處理

數(shù)據(jù)綁定允許你編寫表達(dá)式來處理view分派的事件瓤漏。事件屬性名字取決于監(jiān)聽器方法名字腾夯。例如View.OnLongClickListeneronLongClick()的方法,因此這個事件的屬性是android:onLongClick蔬充。處理事件有兩種方法:

方法引用:在表達(dá)式中蝶俱,可以引用符合偵聽器方法簽名的方法。當(dāng)表達(dá)式計算到方法引用時饥漫,數(shù)據(jù)綁定將方法引用和所有者對象包裝在偵聽器中榨呆,并將該偵聽器設(shè)置為目標(biāo)視圖。如果表達(dá)式計算為null庸队,那么數(shù)據(jù)綁定不會創(chuàng)建一個監(jiān)聽器积蜻,而是設(shè)置一個空監(jiān)聽器。

偵聽器綁定:這些是在事件發(fā)生時評估的lambda表達(dá)式彻消。數(shù)據(jù)綁定總是創(chuàng)建一個監(jiān)聽器竿拆,它在視圖上設(shè)置。當(dāng)事件被發(fā)送時宾尚,監(jiān)聽器將計算lambda表達(dá)式

方法引用

事件可以直接綁定到處理程序方法丙笋,類似于android:onClick可以被分配到活動中的方法。與視圖# onClick屬性相比央勒,一個主要優(yōu)點(diǎn)是在編譯時處理表達(dá)式不见,因此如果該方法不存在或它的簽名不正確,則會收到一個編譯時錯誤崔步。
方法引用和偵聽器綁定之間的主要區(qū)別在于稳吮,實際的偵聽器實現(xiàn)是在數(shù)據(jù)綁定時創(chuàng)建的,而不是在事件觸發(fā)時創(chuàng)建井濒。如果您喜歡在事件發(fā)生時評估表達(dá)式灶似,則應(yīng)該使用偵聽器綁定列林。
要將事件分配給它的處理程序,使用一個普通的綁定表達(dá)式酪惭,其中值是要調(diào)用的方法名稱希痴。例如,如果您的數(shù)據(jù)對象有兩種方法:

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

綁定表達(dá)式可以為視圖指定單擊偵聽器:

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

偵聽器綁定

偵聽器綁定是在事件發(fā)生時運(yùn)行的綁定表達(dá)式春感。它們類似于方法引用砌创,但它們允許您運(yùn)行任意數(shù)據(jù)綁定表達(dá)式。這個功能可以使用Android Gradle插件來升級版本2.0和更高版本鲫懒。

在方法引用中嫩实,方法的參數(shù)必須與事件監(jiān)聽器的參數(shù)相匹配。在偵聽器綁定中窥岩,只有您的返回值必須匹配偵聽器的預(yù)期返回值(除非它期望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表達(dá)式表示晃洒,這些表達(dá)式只能作為表達(dá)式的根元素。當(dāng)在表達(dá)式中使用回調(diào)時朦乏,數(shù)據(jù)綁定會自動為事件創(chuàng)建必要的偵聽器和寄存器球及。當(dāng)視圖觸發(fā)事件時,數(shù)據(jù)綁定將計算給定的表達(dá)式集歇。與常規(guī)綁定表達(dá)式一樣桶略,在對這些偵聽器表達(dá)式進(jìn)行評估時,仍然可以獲得數(shù)據(jù)綁定的null和線程安全性诲宇。

請注意际歼,在上面的示例中,我們還沒有定義傳入onClick的視圖參數(shù)(android.view.View)姑蓝。偵聽器綁定為偵聽器參數(shù)提供了兩個選擇:您可以忽略所有參數(shù)鹅心,也可以對所有參數(shù)進(jìn)行命名。如果您喜歡命名參數(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)}" />

如果您正在監(jiān)聽的事件返回一個類型不是void的值输枯,那么您的表達(dá)式必須返回相同類型的值。例如占贫,如果您想要偵聽長時間單擊事件桃熄,您的表達(dá)式應(yīng)該返回布爾值。

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

如果由于null對象不能計算表達(dá)式型奥,那么數(shù)據(jù)綁定將返回該類型的默認(rèn)Java值瞳收。例如碉京,引用類型為null,int為0,布爾值為false等等螟深。

如果需要使用謂詞的表達(dá)式(如ternary)谐宙,則可以將void用作符號。

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

避免復(fù)雜的監(jiān)聽

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

存在一些專門的單擊事件處理程序舰讹,它們需要一個非android:onClick的屬性來避免沖突。為了避免這種沖突闪朱,創(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)深入Layout文件

a)Import

零個或多個import元素可能在data元素中使用月匣。這些只用在你的layout文件中添加引用,就像在Java中:

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

現(xiàn)在奋姿,View可以使用你的Binding表達(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"/>

這樣,在該layout文件中Vista對應(yīng)com.example.real.estate.View称诗,而View對應(yīng)android.view.View萍悴。導(dǎo)入的類型可以在Variable和表達(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還沒有處理imports,所以自動導(dǎo)入Variable在你的IDE不能使用寓免。您的app仍會正常編譯癣诱,你可以在您的Variable定義中使用完全符合規(guī)定的名稱來解決該IDE問題。

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

導(dǎo)入的類型還可以在表達(dá)式中使用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.*是自動導(dǎo)入的撕予。

b)Variables

data中可以使用任意數(shù)量的variable元素。每一個variable元素描述了一個用于layout文件中Binding表達(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>

Variable類型在編譯時檢查实抡,因此如果一個Variable實現(xiàn)了Observableobservable collection,這應(yīng)該反映在類型中欢策。(譯注:需要查找資料來理解)如果variable是一個沒有實現(xiàn)Observable接口的基本類或者接口吆寨,Variables不會被observed!

當(dāng)對于多種配置有不同的layout文件時(如踩寇,橫向或縱向)啄清,Variables會被合并。這些layout文件之間必須不能有沖突的Variable定義姑荷。

產(chǎn)生的Binding類對于每一個描述的Variables都會有setter和getter盒延。這些Variables會使用默認(rèn)的Java值 - null(引用類型)缩擂、0(int)、false(boolean)等等添寺,直到調(diào)用setter時胯盯。

c)自定義Binding類名稱

默認(rèn)情況下,Binding類的命名是基于所述layout文件的名稱计露,用大寫開頭博脑,除去下劃線()以及()后的第一個字母大寫,然后添加“Binding”后綴票罐。這個類將被放置在一個模塊封裝包里的databinding封裝包下叉趣。例如,所述layout文件contact_item.xml將生成ContactItemBinding该押。如果模塊包是com.example.my.app疗杉,那么它將被放置在com.example.my.app.databinding

Binding類可通過調(diào)整data元素中的class屬性來重命名或放置在不同的包中蚕礼。例如:

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

在模塊封裝包的databinding包中會生成名為ContactItem的Binding類烟具。如果要想讓該類生成在不同的包種,你需要添加前綴.奠蹬,如下:

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

在這個情況下朝聋,ContactItem類直接在模塊包種生成《谠辏或者你可以提供整個包名:

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

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

Data binding不支持包括作為合并元素的直接子元素冀痕。例如,不支持以下布局:

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

e)表達(dá)式

  • 常用表達(dá)式跟Java表達(dá)式很像狸演,以下這些是一樣的:

    • [ ] 數(shù)學(xué) + - / * %

    • [ ] 字符串連接 +

    • [ ] 邏輯 && ||

    • [ ] 二進(jìn)制 & | ^

    • [ ] 一元運(yùn)算 + - ! ~

    • [ ] 移位 >> >>> <<

    • [ ] 比較 == > < >= <=

    • [ ] instanceof

    • [ ] 分組 ()

    • [ ] null

    • [ ] Cast

    • [ ] 方法調(diào)用

    • [ ] 數(shù)據(jù)訪問 []

    • [ ] 三元運(yùn)算 ?:

    示例:

    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合并操作

    ?? - 左邊的對象如果它不是null,選擇左邊的對象严沥;或者如果它是null猜极,選擇右邊的對象:

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

    上的寫法相當(dāng)于下面的寫法:

    android:text="@{user.displayName != null ? user.displayName : user.lastName}"
    
  • 屬性引用

    第一個已經(jīng)在前邊提到了a)Data Binding表達(dá)式:JavaBean引用的簡短格式。
    當(dāng)一個表達(dá)式引用一個類的屬性消玄,它仍使用同樣的格式對于字段跟伏、getters以及ObservableFields。

    android:text="@{user.lastName}"
    
  • 避免 NullPointerException

    Data Binding代碼生成時自動檢查是否為nulls來避免出現(xiàn)null pointer exceptions錯誤翩瓜。例如受扳,在表達(dá)式@{user.name}中,如果user是null兔跌,user.name會賦予它的默認(rèn)值(null)勘高。如果你引用user.age,age是int類型,那么它的默認(rèn)值是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&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āng)使用單引號包含屬性值時,在表達(dá)式中使用雙引號很容易:

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

    使用雙引號來包含屬性值也是可以的宾抓。字符串前后需要使用"`":

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

    使用正常的表達(dá)式來訪問resources也是可行的:

    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ù)都會通過:

    Have an orange
    Have %d oranges
    
    android:text="@{@plurals/orange(orangeCount, orangeCount)}"
    
    類型 正常引用 表達(dá)式引用
    String[] @array @stringArray
    int[] @array @intArray
    TypedArray @array @typedArray
    Animator @animator @animator
    StateListAnimator @animator @stateListAnimator
    color int @color @color
    ColorStateList @color @colorStateList

    一些資源需要顯式類型判斷:

    類型 正常引用 表達(dá)式引用
    String[] @array @stringArray
    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不會導(dǎo)致UI更新石洗。Data Binding的真正能力是當(dāng)數(shù)據(jù)變化時幢泼,可以通知給你的Data對象。有三種不同的數(shù)據(jù)變化通知機(jī)制:Observable對象讲衫、ObservableFields以及observable collections缕棵。

當(dāng)這些可觀察Data對象綁定到UI,Data對象屬性的更改后涉兽,UI也將自動更新挥吵。

a)Observable 對象

實現(xiàn)android.databinding.Observable接口的類可以允許附加一個監(jiān)聽器到Bound對象以便監(jiān)聽對象上的所有屬性的變化红符。

Observable接口有一個機(jī)制來添加和刪除監(jiān)聽器扁瓢,但通知與否由開發(fā)人員管理呵恢。為了使開發(fā)更容易,一個BaseObservable的基類為實現(xiàn)監(jiān)聽器注冊機(jī)制而創(chuàng)建矿辽。Data實現(xiàn)類依然負(fù)責(zé)通知當(dāng)屬性改變時。這是通過指定一個Bindable注解給getter以及setter內(nèi)通知來完成的郭厌。

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類文件中生成一個Entry。BR類文件會在模塊包內(nèi)生成折柠。如果用于Data類的基類不能改變宾娜,Observable接口通過方便的PropertyChangeRegistry來實現(xiàn)用于儲存和有效地通知監(jiān)聽器。

b)Observable 字段

一些小工作會涉及到創(chuàng)建Observable類扇售,因此那些想要節(jié)省時間或者幾乎沒有幾個屬性的開發(fā)者可以使用·ObservableFields·前塔。·ObservableFields·是自包含具有單個字段的observable對象承冰。它有所有基本類型和一個是引用類型华弓。要使用它需要在data對象中創(chuàng)建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();

c)Observable 集合

一些app使用更多的動態(tài)結(jié)構(gòu)來保存數(shù)據(jù)寂屏。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&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用于鍵是整數(shù):

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&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)Binding生成

Binding類的生成鏈接了layout中variables與Views。如前面所討論的考廉,Binding的名稱和包名可以定制秘豹。所生成的Binding類都擴(kuò)展了android.databinding.ViewDataBinding

a)創(chuàng)建

Binding應(yīng)在inflation之后就立馬創(chuàng)建芝此,以確保View層次結(jié)構(gòu)不在之前打擾layout中的binding到views上的表達(dá)式憋肖。有幾個方法可以綁定到一個layout。最常見的是在Binding類上使用靜態(tài)方法.inflate方法載入View的層次結(jié)構(gòu)并且綁定到它只需這一步婚苹。還有一個更簡單的版本岸更,只需要LayoutInflater還有一個是采用ViewGroup

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

如果使用不同的機(jī)制載入layout,他可一分開綁定:

MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);

有時Binding不能提前知道膊升,對于這種情況怎炊,可以使用DataBindingUtil類來創(chuàng)建Binding:

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

b)帶ID的Views

在layout中對于每個帶ID的View會生成一個public final字段。Binding在View層次結(jié)構(gòu)上做單一的傳遞廓译,提取帶ID的Views评肆。這種機(jī)制比起某些Views使用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>

它會生成如下的Binding類:

public final TextView firstName;
public final TextView lastName;

IDs不像沒有Data Bindings那樣幾乎沒有必要非区,但是仍然會有一些實例需要從代碼中訪問Views瓜挽。

c)Variables

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

它會在Binding中生成setters和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);

d)ViewStubs

ViewStubs跟正常的Views略有不同征绸。他們開始時是不可見的久橙,當(dāng)他們要么設(shè)置為可見或被明確告知要載入時,它們通過載入另外一個layout取代了自己管怠。

由于ViewStub基本上從View的層次結(jié)構(gòu)上消失淆衷,在Binding對象的View也必須消失來允許被收集。因為Views是最后的渤弛,一個ViewStubProxy對象取帶ViewStub祝拯,給開發(fā)者獲得了ViewStub,當(dāng)它存在以及還可以訪問載入的View層次結(jié)構(gòu)時當(dāng)ViewStub已被載入時她肯。

當(dāng)載入另一個layout佳头,為新的布局必需創(chuàng)建一個Binding。因此晴氨,ViewStubProxy必需監(jiān)聽ViewStubOnInflateListener監(jiān)聽器并在那個時候建立Binding畜晰。因為只有一個可以存在,ViewStubProxy允許開發(fā)者在其上設(shè)置一個OnInflateListener它會在建立Binding后調(diào)用瑞筐。

e)Binding進(jìn)階

  • 動態(tài)Variables

    有時凄鼻,不知道具體的Binding類腊瑟,例如,一個RecyclerView適配器對layouts任意操作并不知道具體的Binding類块蚌。它仍然必需在onBindViewHolder期間賦值給Binding闰非。

    在這個例子中,該RecyclerView綁定的所有l(wèi)ayouts有一個“item”的Variable峭范。該BindingHolder有一個getBinding方法返回ViewDataBinding财松。

    public void onBindViewHolder(BindingHolder holder, int position) {
       final T item = mItems.get(position);
       holder.getBinding().setVariable(BR.item, item);
       holder.getBinding().executePendingBindings();
    }
    
  • 直接Binding

    當(dāng)一個variable或observable變化時,binding會在下一幀之前被計劃要改變纱控。有很多次辆毡,但是在Binding時必須立即執(zhí)行。要強(qiáng)制執(zhí)行甜害,使用executePendingBindings()方法舶掖。

  • 后臺線程

    只要它不是一個集合,你可以在后臺線程中改變你的數(shù)據(jù)模型尔店。在判斷是否要避免任何并發(fā)問題時眨攘,Data Binding會對每個Varialbe/field本地化。

7)屬性Setters

每當(dāng)綁定值的變化嚣州,生成的Binding類必須調(diào)用setter方法??鲫售。Data Binding框架有可以自定義賦值的方法。

a)自動Setters

對于一個屬性该肴,Data Binding試圖找到setAttribute方法情竹。與該屬性的namespace并不什么關(guān)系,僅僅與屬性本身名稱有關(guān)匀哄。

例如鲤妥,有關(guān)TextView的android:text屬性的表達(dá)式會尋找一個setText(String)的方法。如果表達(dá)式返回一個int拱雏,Data Binding會搜索的setText(int)方法。注意:要表達(dá)式返回正確的類型底扳,如果需要的話使用casting铸抑。Data Binding仍會工作即使沒有給定名稱的屬性存在。然后衷模,您可以通過Data Binding輕松地為任何setter“創(chuàng)造”屬性鹊汛。例如,DrawerLayout沒有任何屬性阱冶,但大量的setters刁憋。您可以使用自動setters來使用其中的一個。

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

b)重命名的Setters

一些有setters的屬性按名稱并不匹配木蹬。對于這些方法至耻,屬性可以通過BindingMethods注解相關(guān)聯(lián)。這必須與一個包含BindingMethod注解的類相關(guān)聯(lián),每一個用于一個重命名的方法尘颓。例如走触,android:tint屬性與setImageTintList相關(guān)聯(lián),而不與setTint相關(guān)疤苹。

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

以上例子互广,開發(fā)者需要重命名setters是不太可能了,android架構(gòu)屬性已經(jīng)實現(xiàn)了卧土。

c)自定義Setters

有些屬性需要自定義綁定邏輯惫皱。例如,對于android:paddingLeft屬性并沒有相關(guān)setter尤莺。相反旅敷,setPadding(left, top, right, bottom)是存在在。一個帶有BindingAdapter注解的靜態(tài)綁定適配器方法允許開發(fā)者自定義setter如何對于一個屬性的調(diào)用缝裁。

Android的屬性已經(jīng)創(chuàng)造了BindingAdapters扫皱。舉例來說,對于paddingLeft

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

Binding適配器對其他定制類型非常有用捷绑。例如韩脑,自定義loader可以用來異步載入圖像。

當(dāng)有沖突時粹污,開發(fā)人員創(chuàng)建的Binding適配器將覆蓋Data Binding默認(rèn)適配器段多。

您也可以創(chuàng)建可以接收多個參數(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}"/>

如果對于一個ImageViewimageUrl和error都被使用并且imageUrl是一個string類型以及error是一個drawable時壮吩,該適配器會被調(diào)用进苍。

  • 匹配的過程中自定義namespaces將被忽略。
  • 你也可以為Android namespaces寫適配器鸭叙。

綁定適配器方法可以在處理程序中選擇舊的值觉啊。一個取舊值和新值的方法應(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);
}

因為改變一個偵聽器也會影響另一個偵聽器学歧,因此我們必須有三個不同的綁定適配器罩引,一個用于每個屬性,一個用于兩者枝笨,它們都應(yīng)該設(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ù)雜一點(diǎn),因為視圖使用添加和刪除偵聽器View.OnAttachStateChangeListener相反的一組方法揭蜒。android.databinding.adapters.ListenerUtil類幫助跟蹤以前的偵聽器,以便在綁定Adaper中刪除它們昭躺。

通過注解的接口OnViewDetachedFromWindowOnViewAttachedToWindow @TargetApi(VERSION_CODES.HONEYCOMB_MR1),數(shù)據(jù)綁定代碼生成器知道聽眾只能運(yùn)行在蜂窩MR1時生成和新設(shè)備,支持的相同版本addOnAttachStateChangeListener(View.OnAttachStateChangeListener)忌锯。

8)轉(zhuǎn)換

a)對象轉(zhuǎn)換

當(dāng)從Binding表達(dá)式返回一個對象,一個setter會從自動领炫、重命名以及自定義的setters中選擇偶垮。該對象將被轉(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)有關(guān)參數(shù)類型可能混亂時,開發(fā)人員需要在表達(dá)式中轉(zhuǎn)換葱峡。

b)自定義轉(zhuǎn)換

有時候轉(zhuǎn)換應(yīng)該是自動的在特定類型之間砚哗。例如,設(shè)置背景的時候:

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

這里砰奕,背景需要Drawable對象蛛芥,但顏色是一個整數(shù)。不管何時有Drawable并且返回值是一個整數(shù)军援,那么整數(shù)類型會被轉(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支持

Android Studio為數(shù)據(jù)綁定支持許多的代碼編輯胸哥。例如涯竟,它支持以下功能:

  • 語法高亮
  • 標(biāo)記表達(dá)式的語法錯誤
  • XML代碼補(bǔ)全
  • 引用,包括navigation(如導(dǎo)航到聲明處)以及快速文檔查詢

注意: 數(shù)組以及通用類型空厌,比如說Observable類庐船,可能會顯示錯誤事實上并沒有錯誤。

預(yù)覽面板會顯示數(shù)據(jù)綁定的默認(rèn)值嘲更。在以下例子中筐钟,面板會在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á)數(shù)值赋朦,參考:Designtime Layout Attributes

---------------------<完>-----------------------

原文鏈接:Data Binding Guide (Android)

(如有翻譯有誤或者不理解的地方篓冲,請指正)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市北发,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌喷屋,老刑警劉巖琳拨,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異屯曹,居然都是意外死亡狱庇,警方通過查閱死者的電腦和手機(jī)惊畏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來密任,“玉大人颜启,你說我怎么就攤上這事±嘶洌” “怎么了缰盏?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長淹遵。 經(jīng)常有香客問我口猜,道長,這世上最難降的妖魔是什么透揣? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任济炎,我火速辦了婚禮,結(jié)果婚禮上辐真,老公的妹妹穿的比我還像新娘须尚。我一直安慰自己,他們只是感情好侍咱,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布耐床。 她就那樣靜靜地躺著,像睡著了一般放坏。 火紅的嫁衣襯著肌膚如雪咙咽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天淤年,我揣著相機(jī)與錄音钧敞,去河邊找鬼。 笑死麸粮,一個胖子當(dāng)著我的面吹牛溉苛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播弄诲,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼愚战,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了齐遵?” 一聲冷哼從身側(cè)響起寂玲,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎梗摇,沒想到半個月后拓哟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡伶授,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年断序,在試婚紗的時候發(fā)現(xiàn)自己被綠了流纹。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡违诗,死狀恐怖漱凝,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情诸迟,我是刑警寧澤茸炒,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站亮蒋,受9級特大地震影響扣典,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜慎玖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一贮尖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧趁怔,春花似錦湿硝、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至铺浇,卻和暖如春痢畜,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鳍侣。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工丁稀, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人倚聚。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓线衫,卻偏偏與公主長得像,于是被迫代替她去往敵國和親惑折。 傳聞我的和親對象是個殘疾皇子授账,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容