Data Binding Library官方文檔中文翻譯

Google Data Binding Library官方文檔原文地址

Data Binding Library 數(shù)據(jù)綁定庫(kù)

本文檔解釋了如何使用Data Binding Library編寫聲明式布局,并盡量減少綁定應(yīng)用程序邏輯和布局所需的膠合代碼。
Data Binding Library提供了靈活性和廣泛的兼容性 - 它是一個(gè)支持庫(kù),所以您可以將它與所有Android平臺(tái)版本一起使用回到Android 2.1(API級(jí)別7+)。
使用DataBinding要求Gradle插件為1.5.0-alpha1及以上版本。查看如何升級(jí)Gradle插件。

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

使用Data Binding之前,需要用Android SDK manager 在Support repository中下載依賴庫(kù)。
配置app使用data binding,在app模塊下的build.gradle文件中添加dataBinding元素。

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

如果你的app模塊依賴的library使用了data binding,你的app模塊也必須在它的build.gradle文件中配置data binding。
同時(shí),確定你使用的是兼容版本的Android Studio,Android Studio 1.3及更高版本支持data binding ,如Android Studio Support for Data Binding中所說的那樣。

Data Binding Compiler V2 數(shù)據(jù)綁定編譯器V2

Android Gradle Plugin 3.1.0 Canary 6 附帶了一個(gè)可選的新編譯器。要開始使用它,更新你的gradle.properties 文件使它包含下面這行內(nèi)容

 android.databinding.enableV2=true

在編譯器V2中:

  • ViewBinding 類由Android Gradle Plugin在java編譯器之前自動(dòng)生成。如果java編譯由于不相關(guān)的原因失敗,這樣可以避免很多真實(shí)的錯(cuò)誤.
  • 在v1中,編譯應(yīng)用程序時(shí)將重新生成庫(kù)的綁定類(分享生成的代碼并訪問最終的BRR文件).在v2中,庫(kù)保留其生成的綁定類以及映射器信息,從而顯著提高多模塊項(xiàng)目的數(shù)據(jù)綁定性能。

請(qǐng)注意,這個(gè)新編譯器向后不兼容,因此使用v1編譯的庫(kù)不能被v2使用,反之亦然。
V2也減少了一些很少使用的功能:

  • 在v1中,一個(gè)應(yīng)用程序能夠提供可以覆蓋依賴中的適配器的綁定適配器.在v2中,它只會(huì)對(duì)您自己的模塊/應(yīng)用程序及其依賴項(xiàng)中的代碼生效.
  • 以前,如果一個(gè)布局文件在2個(gè)或更多不同資源配置中包含一個(gè)具有相同id,不同類的View.Data Binding將會(huì)查找最常見的父類.在V2中,當(dāng)類型和配置不匹配時(shí),它會(huì)默認(rèn)是view.
  • 在v2中,不同的模塊將不能在manifest使用相同的包名,因?yàn)镈ata Binding將會(huì)使用該包名來生成綁定映射器類.

Data Binding Layout Files 數(shù)據(jù)綁定布局文件

Writing your first set of data binding expressions 編寫第一個(gè)數(shù)據(jù)綁定表達(dá)式

Data-binding 布局文件是略有不同的,以layout根標(biāo)簽開始,后面跟著data元素和view根元素.這個(gè)view元素是你在一個(gè)非綁定布局中的根布局,舉個(gè)栗子:

<?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 variable描述了一個(gè)屬性,可以在這個(gè)布局中使用.

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

布局中的表達(dá)式使用@{}語(yǔ)法.下面是將TextView的text設(shè)置成user的firstName屬性

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

Data Object 數(shù)據(jù)對(duì)象

讓我們假設(shè)有一個(gè)普通的java對(duì)象類User:

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

這種類型的對(duì)象的數(shù)據(jù)從不變化.在應(yīng)用程序中讀取一次,數(shù)據(jù)一直不變的數(shù)據(jù)很正常.也可以使用JavaBeans對(duì)象:

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ù)板頂?shù)慕嵌瓤?有兩個(gè)類是等價(jià)的.TextView中android:text屬性使用的@{user.firstName}表達(dá)式將會(huì)獲取前一個(gè)類的firstName變量和后面類中的 getFirstName()方法.無(wú)論哪種方式,如果該方法存在,都會(huì)被解析成firstName()方法.

Binding Data 綁定數(shù)據(jù)

默認(rèn)情況下,一個(gè)Binding類會(huì)以資源文件的名稱為基礎(chǔ)創(chuàng)建,將其轉(zhuǎn)化為駝峰命名并以Binding結(jié)尾.上面資源文件是main_activity.xml所以自動(dòng)生成的類是MainActivityBinding.這個(gè)類包含了布局中的所有視圖的所有綁定的屬性(如,user變量),并且知道如何為綁定表達(dá)式賦值.創(chuàng)建綁定最容易的方法是在它inflating的時(shí)候:

@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)行程序,你可以在UI中看到Test User.或者,你可以通過下面的方式:

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

如果你使用數(shù)據(jù)綁定ListView獲取RecyclerView 適配器的條目,你或許更喜歡下面的方法:

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

Event Handling 事件綁定

Data Binding允許您編寫處理從view分發(fā)事件的表達(dá)式(如onClick)。事件屬性名稱由監(jiān)聽方法的名稱控制,只有少數(shù)例外。例如,View.OnLongClickListener有一個(gè)onLongClick()方法.所以這個(gè)時(shí)間的屬性是android:onLongClick,有兩種方法去處理事件.

  • Method References:在表達(dá)式中,可以引用符合listener方法簽名的方法。當(dāng)表達(dá)式計(jì)算為方法引用時(shí),Data Binding將方法引用和所有者對(duì)象包裝在listener中,并且將listener設(shè)置到目標(biāo)view上.如果表達(dá)式計(jì)算為null,則Data Binding不會(huì)創(chuàng)建listener,而是設(shè)置一個(gè)空l(shuí)istener。
  • Listener Bindings:這是在事件發(fā)生時(shí)被計(jì)算的lambda表達(dá)式.Data Binding 總是創(chuàng)建一個(gè)listener,并將它設(shè)置到view上.當(dāng)事件被分發(fā)時(shí),listener計(jì)算lambda表達(dá)式

Method References 方法引用

事件可以直接綁定到處理程序方法,類似于android:onClick可以分配到一個(gè)Activity中.相比較view#onClick一個(gè)主要的優(yōu)勢(shì)是該表達(dá)式在編譯時(shí)被處理,所以如果這個(gè)方法不存在或者簽名不正確,你會(huì)收到一個(gè)編譯時(shí)錯(cuò)誤.

Method References和Listener Bindings主要的區(qū)別是實(shí)際的監(jiān)聽器是在數(shù)據(jù)綁定時(shí)創(chuàng)建的,而不是在事件觸發(fā)時(shí).如果你喜歡在事件發(fā)生時(shí)計(jì)算表達(dá)式,你應(yīng)該使用listener binding.

將事件分配給它的處理程序,使用一個(gè)正常的綁定表達(dá)式,以值為方法名調(diào)用.舉個(gè)例子,如果你的data對(duì)象有兩個(gè)方法:

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

綁定表達(dá)式可以為view指派一個(gè)click listener:

<?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á)式中的方法簽名必須和監(jiān)聽事件中的方法簽名完全匹配.

Listener Bindings 監(jiān)聽事件綁定

Listener Bindings 是當(dāng)事件發(fā)生時(shí)才運(yùn)行的綁定表達(dá)式.它和方法引用相似 ,但是它可以運(yùn)行任何數(shù)據(jù)綁定的表達(dá)式.此特性適用于Gradle 2.0版及更高版的Android Gradle插件.
在方法引用中,該方法的參數(shù)必須和listener的參數(shù)匹配.在監(jiān)聽器綁定中,只有你的返回值必須和listener匹配(除非返回值是void).舉個(gè)栗子,你可以有一個(gè)具有下面方法的presenter類:

public class Presenter {
    public void onSaveClick(Task task){}
}

然后,你可以按照下面的寫法綁定click事件:

  <?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)聽器由僅允許作為表達(dá)式的根元素的lambda表達(dá)式代理.當(dāng)表達(dá)式中使用回調(diào)時(shí),Data Binding自動(dòng)為該事件創(chuàng)建相關(guān)的監(jiān)聽器和注冊(cè)器.當(dāng)view觸發(fā)該事件,Data Bindign解析給出的表達(dá)式.在一般的綁定表達(dá)式中,在編譯監(jiān)聽表達(dá)式時(shí),你任然可以獲取null和線程安全性.
注意上面的例子,我們沒有定義傳入onClick(android.view.View)view參數(shù).Listener bindings 為監(jiān)聽參數(shù)提供了兩種選擇:你可以省略忽略該方法的所有參數(shù)或者命名所有參數(shù).如果你喜歡聲明參數(shù),你可以在你的表達(dá)式中使用它.舉個(gè)栗子,上面的表達(dá)式將會(huì)改為:

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

或者你如果想在表達(dá)式中使用這個(gè)參數(shù),像下面這樣寫:

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

你可以使用多參的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)聽的事件需要有一個(gè)返回值,返回值類型不是void,你的表達(dá)式也必須返回相同類型的返回值.舉個(gè)栗子,如果你想監(jiān)聽long click 事件,你的表達(dá)式必須返回boolean值.

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

如果表達(dá)式不能被解析成null對(duì)象.Data Binding返回該類型的默認(rèn)值.舉個(gè)栗子,引用類型為null,int類型為0,boolen類型是false,等等.
如果你需要使用帶斷言的表達(dá)式(如,三元運(yùn)算符),你可以使用void作為符號(hào).

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

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

Listener表達(dá)式非常強(qiáng)大,可以讓你的代碼非常容易閱讀。另一方面,監(jiān)聽器包含復(fù)雜的表達(dá)式會(huì)使你的布局難以閱讀和無(wú)法維護(hù).這些表達(dá)式應(yīng)該像從UI傳遞數(shù)據(jù)到回調(diào)方法一樣簡(jiǎn)單.你應(yīng)該在把業(yè)務(wù)邏輯放到監(jiān)聽表達(dá)式調(diào)用的回調(diào)方法中處理.
存在一些專門的單擊事件,他們需要一個(gè)除了android:onClick來避免沖突.下面的屬性就是為了避免沖突而創(chuàng)建的:

image.png

Layout Details 布局詳情

Imports 導(dǎo)包

data元素內(nèi)可以使用0個(gè)或多個(gè)import元素.這些可以輕松引用布局文件中的類,就像在Java中一樣.

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

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

<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)類名沖突的時(shí)候,其中一個(gè)類可以重命名為一個(gè)"別名":

<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在布局文件中.導(dǎo)入的類型可以用作變量和表達(dá)式中的引用.

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

注意:Android Studio暫不支持自動(dòng)導(dǎo)包.你的應(yīng)用可以正常使用,你可以在你的變量定義中通過使用全包名來解決IDE的問題.

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

導(dǎo)入的類型也可以在表達(dá)式中用于靜態(tài)變量和靜態(tài)方法上:

<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òng)導(dǎo)入的.

Variables 變量

data元素內(nèi)可以使用任意數(shù)量的variable元素.layout中的每個(gè)variable元素描述了一個(gè)屬性,都可以在布局文件中在綁定表達(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>

變量的類型在編譯器被檢查,如果一個(gè)變量實(shí)現(xiàn)了android.databinding.Observable或者是observable collection,那應(yīng)該被反應(yīng)在類型中.如果變量是不實(shí)現(xiàn)Observable*接口的基類或接口,將不會(huì)被觀察!
當(dāng)布局文件有不同的配置(橫向或縱向),變量將被合并.這些 布局文件直接必須不存在沖突的變量定義.
自動(dòng)生成的綁定類對(duì)于每個(gè)描述的變量都提供了setter和getter方法.變量將具有java默認(rèn)值,直到setter方法被調(diào)用-引用類型中的null,int類型的0,booleanfalse,等等.
根據(jù)需求為我們的綁定表達(dá)式生成一個(gè)特殊的變量context. context的值是根布局的getContext()方法提供的Context.context變量獎(jiǎng)杯具有該名稱的顯示變量所覆蓋.

Custom Binding Class Names 自定義綁定類的名稱

默認(rèn)情況下,綁定類是以布局文件的名稱為基礎(chǔ)生成的,以大寫字母開頭,刪除下劃線并將后面字母大寫,最后以"Binding"結(jié)尾.這個(gè)類將會(huì)生成在模塊包下的數(shù)據(jù)綁定包下.舉個(gè)栗子,布局文件contact_item.xml將會(huì)生成ContactItemBinding.如果模塊的包名是com.example.my.app,該類將會(huì)創(chuàng)建在com.example.my.app.databinding包下面.
通過調(diào)整data元素的class屬性,綁定類可以重命名或放在不同包下.舉個(gè)栗子:

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

這將會(huì)在模塊的包下面,databindingg包下生成一個(gè)ContactItem類.如果想要在模塊包內(nèi)不同的包下生成這個(gè)類,需要在前面加".":

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

在這個(gè)例子中, ContactItem是直接在模塊包中生成的.如果你提供的是一個(gè)全包名的話,可以在任意包下生成:

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

Includes include標(biāo)簽

變量從包含的布局傳遞到include的布局的綁定中,通過程序的命名空間和屬性的變量名:

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

現(xiàn)在,在name.xmlcontact.xml布局文件中必須有一個(gè)user變量.
Data Binding不支持include做為merge元素的直接子元素.舉個(gè)栗子,下面的布局是不支持的:

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

Expression Language 表達(dá)式語(yǔ)法

Common Features 通用特性

表達(dá)式語(yǔ)法非常像Java表達(dá)式.相同的有:

  • 數(shù)學(xué)運(yùn)算 + - / * %
  • 字符串拼接 +
  • 邏輯運(yùn)算 && ||
  • 二進(jìn)制 & | ^
  • 一元運(yùn)算 + - ! ~
  • 位運(yùn)算 >> >>> <<
  • 比較 == > < >= <=
  • instanceof
  • 分組 ()
  • 數(shù)據(jù)類型 character,String,numeric,null
  • 類型轉(zhuǎn)換
  • 方法回調(diào)
  • 數(shù)據(jù)屬性
  • 數(shù)組 []
  • 三元操作符 ?:
    例子:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'

Missing Operations 缺失的語(yǔ)法

缺少了一小部分Java中的表達(dá)式語(yǔ)法.

  • this
  • super
  • new
  • 顯式泛型調(diào)用

Null Coalescing Operator 空合并運(yùn)算符

空合并運(yùn)算符(??)如果不是空的話就用左邊的,是空的話就用右邊的.

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

功能上等同于:

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

Property Reference 屬性引用

在"Writing your first set of data binding expressions"節(jié)中已經(jīng)討論過了,簡(jiǎn)單的JavaBean引用.當(dāng)在一個(gè)類中,一個(gè)表達(dá)式引用一個(gè)屬性時(shí),他使用相同的格式對(duì)于字段,獲取,或者觀察者的字段.

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

Avoiding NullPointerException 避免空指針

生成的數(shù)據(jù)綁定的代碼hi自動(dòng)檢測(cè)null值,避免空指針.舉個(gè)栗子,在表達(dá)式@{user.name}中,如果user的值為null,user.name將分配其默認(rèn)值(null).如果你引用的是user.age,age的是int類型,默認(rèn)值將會(huì)是0.

Collections 集合

普通集合:arrays,list,sparse list,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]}"

String Literals 字符串文本

在屬性值的外面使用單引號(hào),在表達(dá)式中使用雙引號(hào)就會(huì)很容易:

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

也可以在屬性值的外面使用雙引號(hào),如果這樣做了,字符串需要用正引號(hào)或反引號(hào):

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

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

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

可以通過提供參數(shù)來進(jìn)行格式化字符串,復(fù)數(shù):

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

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

Have an orange
Have %d oranges
android:text="@{@plurals/orange(orangeCount, orangeCount)}"

有些資源需要明確解析類型.


image.png

Data Objects 數(shù)據(jù)對(duì)象

任何普通JavaBeans(POJO)都可以用于數(shù)據(jù)綁定,但是修改POJO不會(huì)導(dǎo)致UI更新.數(shù)據(jù)綁定的真正能力是使用時(shí),可以賦予數(shù)據(jù)對(duì)象變化時(shí)的通知能力.有三種不同的更改通知機(jī)制,Observable對(duì)象,observable字段和observable集合.
當(dāng)這些可觀察數(shù)據(jù)對(duì)象之一綁定到UI并且數(shù)據(jù)對(duì)象的屬性改變時(shí),UI將會(huì)自動(dòng)更新.

Observable Objects 觀察者對(duì)象

一個(gè)類實(shí)現(xiàn)了android.databinding.Observable接口,將允許綁定單個(gè)監(jiān)聽器去綁定對(duì)象來監(jiān)聽對(duì)象上所有的屬性改變.
android.databinding.Observable接口具有添加和刪除偵聽器的機(jī)制,但通知由開發(fā)人員決定。為了簡(jiǎn)化開發(fā),一個(gè)基類,android.databinding.BaseObservable被創(chuàng)建來實(shí)現(xiàn)監(jiān)聽器注冊(cè)機(jī)制.數(shù)據(jù)實(shí)現(xiàn)類仍然可以負(fù)責(zé)當(dāng)屬性改變時(shí)通知.這是通過將一個(gè)android.databinding.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);
   }
}

android.databinding.Bindable注解是編譯期間在BR類文件中生成一個(gè)條目。BR類文件將在模塊包中生成。如果數(shù)據(jù)類的基類無(wú)法更改,則android.databinding.Observable接口可以使用方便的android.databinding.PropertyChangeRegistry實(shí)現(xiàn),以有效地存儲(chǔ)和通知監(jiān)聽器.

ObservableFields 觀察者字段

創(chuàng)建android.databinding.Observable類需要做一點(diǎn)工作,因此想要節(jié)省時(shí)間或具有少量屬性的開發(fā)人員可以使用android.databinding.ObservableField的兄弟姐妹,

android.databinding.ObservableBoolean,
android.databinding.ObservableByte, 
android.databinding.ObservableChar, 
android.databinding.ObservableShort,
android.databinding.ObservableInt, 
android.databinding.ObservableLong, 
android.databinding.ObservableFloat, 
android.databinding.ObservableDouble, 
android.databinding.ObservableParcelable. 

ObservableFields是具有單個(gè)字段的獨(dú)立可觀察對(duì)象。原始版本在訪問操作期間避免裝箱和取消裝箱。要使用,請(qǐng)?jiān)跀?shù)據(jù)類中創(chuàng)建公共最終字段:

private static class User {
   public final ObservableField<String> firstName = new ObservableField<>();
   public final ObservableField<String> lastName = new ObservableField<>();
   public final ObservableInt age = new ObservableInt();
}

就是這樣!要訪問該值,請(qǐng)使用set和get訪問器方法:

user.firstName.set("Google");
int age = user.age.get();

Observable Collections 觀察者集合

一些應(yīng)用程序使用更多動(dòng)態(tài)結(jié)構(gòu)來保存數(shù)據(jù)。觀察者集合允許對(duì)這些數(shù)據(jù)對(duì)象通過鍵訪問。android.databinding.ObservableArrayMap是有用的當(dāng)key是一種引用類型,比如String.

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

在布局中,可以通過String類型的鍵獲取map的數(shù)據(jù):

<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)key是一個(gè)integer類型時(shí)使用android.databinding.ObservableArrayList更好:

ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);

在布局中,list可以通過索引獲取數(shù)據(jù):

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

Generated Binding 生成綁定

生成的綁定類將布局變量與布局中的視圖鏈接起來。如前所述,綁定的名稱和包可能是自定義的。生成的綁定類全部集成android.databinding.ViewDataBinding。

Creating

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

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

如果布局使用不同的機(jī)制進(jìn)行充氣,則可能需要單獨(dú)綁定:

MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);

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

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

Views With IDs

將在布局中為每個(gè)視圖根據(jù)ID生成一個(gè)公開的最終字段。該綁定是視圖結(jié)構(gòu)上通過ID獲取View的唯一的入口.這種機(jī)制可能比調(diào)用多個(gè)view的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>

將會(huì)生成一個(gè)綁定類:

public final TextView firstName;
public final TextView lastName;

ID不像沒有數(shù)據(jù)綁定那樣必要,但仍然有一些情況下代碼仍然需要訪問視圖。

Variables

每個(gè)變量將被賦予訪問器方法。

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

將會(huì)在綁定類中生成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);

ViewStubs

ViewStubs標(biāo)簽和普通的View稍有不同.他們從不可見的時(shí)候開始,當(dāng)他們要么變得可見時(shí),要么被明確告知膨脹時(shí),他們通過膨脹另一種布局來取代布局。因?yàn)橐晥D是最終的,所以android.databinding.ViewStubProxy對(duì)象取代了ViewStub,使開發(fā)人員能夠在ViewStub存在時(shí)訪問ViewStub,并且在ViewStub被充滿時(shí)也可以訪問充滿的View層次結(jié)構(gòu)。
當(dāng)膨脹另一個(gè)布局時(shí),必須為新布局建立綁定。因此,ViewStubProxy必須偵聽ViewStubViewStub.OnInflateListener并在此時(shí)建立綁定。由于只有一個(gè)可以存在,因此ViewStubProxy允許開發(fā)人員在建立綁定后設(shè)置一個(gè)OnInflateListener,它將調(diào)用它。

Advanced Binding 高級(jí)綁定

Dynamic Variables 動(dòng)態(tài)變量

有時(shí),特定的綁定類將不被知道。例如,針對(duì)任意布局的RecyclerView.Adapter不會(huì)知道特定的綁定類。它必須在onBindViewHolder(VH,int)期間分配綁定值。
在這個(gè)例子中,RecyclerView綁定到的所有布局都有一個(gè)“item”變量。 BindingHolder有一個(gè)返回android.databinding.ViewDataBinding庫(kù)的getBinding方法。

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

Immediate Binding 即使綁定

當(dāng)變量或觀察者變化時(shí),綁定將被安排在下一幀之前改變,然而,有時(shí)候,綁定必須立即執(zhí)行.要強(qiáng)制執(zhí)行,請(qǐng)使用android.databinding.ViewDataBinding.executePendingBindings()方法。

Background Thread

只要不是集合,您可以在子線程中更改數(shù)據(jù)模型。數(shù)據(jù)綁定將在編譯時(shí)實(shí)例化每個(gè)變量/字段以避免任何并發(fā)問題

Attribute Setters

對(duì)于一個(gè)屬性,數(shù)據(jù)綁定試圖找到方法setAttribute。屬性的命名空間并不重要,只有屬性名稱本身。
例如,與 TextView 的屬性 android:text 相關(guān)聯(lián)的表達(dá)式將查找 setText(String) 。如果表達(dá)式返回一個(gè)int,則數(shù)據(jù)綁定將搜索setText(int)方法。請(qǐng)注意讓表達(dá)式返回正確的類型,如果需要的話可以強(qiáng)轉(zhuǎn)。請(qǐng)注意,即使給定名稱不存在任何屬性,數(shù)據(jù)綁定也會(huì)起作用。然后,您可以使用數(shù)據(jù)綁定輕松地為任何setter創(chuàng)建屬性。例如,支持DrawerLayout沒有任何屬性,但有很多setter。您可以使用自動(dòng)設(shè)置器來使用其中之一。

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

Renamed Setters 重命名Setter方法

一些屬性具有不匹配名稱的setter。對(duì)于這些方法,可以通過 android.databinding.BindingMethods注解將一個(gè)屬性與setter相關(guān)聯(lián)。這必須與一個(gè)類相關(guān)聯(lián),并且包含android.databinding.BindingMethod注解,每個(gè)重命名的方法一個(gè)注釋。例如,android:tint屬性確實(shí)與setImageTintList(ColorStateList)關(guān)聯(lián),而不是setTint。

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

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

Custom Setters

一些屬性需要自定義綁定邏輯。例如,android:paddingLeft屬性沒有關(guān)聯(lián)的setter。但是,setPadding(左,上,右,下)存在。具有android.databinding.BindingAdapter注解的靜態(tài)綁定適配器方法允許開發(fā)人員定制如何調(diào)用屬性的setter。
android屬性已經(jīng)創(chuàng)建了BindingAdapter。例如,下面是paddingLeft的一個(gè)例子:

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

綁定適配器對(duì)其他類型的自定義很有用。例如,一個(gè)自定義的加載器可以在子線程調(diào)用來加載一個(gè)圖片。發(fā)生沖突時(shí),開發(fā)人員創(chuàng)建的綁定適配器將覆蓋數(shù)據(jù)綁定默認(rèn)適配器。
您也可以讓適配器接收多個(gè)參數(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}"/>

如果imageUrl和error都用于ImageView并且imageUrl是String類型的,并且error是drawable類型的的,則將調(diào)用此適配器。

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

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

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

事件處理程序只能用于一個(gè)抽象方法的接口或抽象類。例如:

@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)一個(gè)監(jiān)聽器有多個(gè)方法時(shí),它必須被拆分成多個(gè)監(jiān)聽器。例如,View.OnAttachStateChangeListener有兩個(gè)方法:onViewAttachedToWindow()onViewDetachedFromWindow()。然后,我們必須創(chuàng)建兩個(gè)接口來區(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àn)楦囊粋€(gè)偵聽器也會(huì)影響另一個(gè)偵聽器,所以我們必須有三個(gè)不同的綁定適配器,一個(gè)用于每個(gè)屬性,另一個(gè)用于兩者,如果它們都被設(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ù)雜一些,因?yàn)閂iew使用add和remove來代替View.OnAttachStateChangeListenerset方法。android.databinding.adapters.ListenerUtil類有助于跟蹤以前的偵聽器,以便它們可以在Binding Adaper中刪除。
通過在接口OnViewDetachedFromWindowOnViewAttachedToWindow中添加@TargetApi(VERSION_CODES.HONEYCOMB_MR1)注解,數(shù)據(jù)綁定代碼生成器知道只能在API11及以上版本使用,該版本支持[addOnAttachStateChangeListener(View.OnAttachStateChangeListener)

Converters 轉(zhuǎn)換器

Object Conversions 對(duì)象轉(zhuǎn)換

從綁定表達(dá)式返回對(duì)象時(shí),將從自動(dòng),重命名和自定義設(shè)置器中選擇一個(gè)setter。該對(duì)象將被轉(zhuǎn)換為所選setter的參數(shù)類型。
這對(duì)于那些使用ObservableMaps來保存數(shù)據(jù)的人來說很方便。例如:

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

userMap返回一個(gè)Object,并且該Object將自動(dòng)轉(zhuǎn)換為在settersetText(CharSequence)中找到的參數(shù)類型。當(dāng)可能對(duì)參數(shù)類型產(chǎn)生混淆時(shí),開發(fā)人員需要在表達(dá)式中進(jìn)行轉(zhuǎn)換。

Custom Conversions

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

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

在這里,背景需要一個(gè)Drawable,但顏色是一個(gè)整數(shù).每當(dāng)需要一個(gè)Drawable并返回一個(gè)整數(shù)時(shí),int應(yīng)該轉(zhuǎn)換為ColorDrawable。這種轉(zhuǎn)換是使用帶有BindingConversion注釋的靜態(tài)方法完成的:

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

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

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

Android Studio Support for Data Binding

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

  • 語(yǔ)法高亮顯示
  • 表達(dá)語(yǔ)言語(yǔ)法錯(cuò)誤的標(biāo)記
  • XML代碼補(bǔ)全
  • 參考資料,包括導(dǎo)航(如導(dǎo)航到聲明)和快速文檔

注意:數(shù)組和一個(gè)泛型類型(如android.databinding.Observable類)可能在沒有錯(cuò)誤的時(shí)候提示錯(cuò)誤。
預(yù)覽窗格顯示數(shù)據(jù)綁定表達(dá)式的默認(rèn)值(如果提供)。在以下示例摘錄布局XML文件中的元素時(shí),“預(yù)覽”窗格在TextView中顯示PLACEHOLDER默認(rèn)文本值。

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

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

最后推薦兩篇DataBinding使用的博客

從零開始的Android新項(xiàng)目7 - Data Binding入門篇

從零開始的Android新項(xiàng)目8 - Data Binding高級(jí)篇

https://blog.csdn.net/qiang_xi/article/category/6995820

https://blog.csdn.net/qiang_xi/article/details/75379321

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

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

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