Android Jetpack架構(gòu)篇:Data Binding(一)

Android Jetpack架構(gòu)篇:Data Binding(一)


<spane id="layouts_binding_exp">Layouts and binding expressions(布局和綁定表達(dá)式)</span>

該表達(dá)式語言,允許你通過編寫表達(dá)式來處理視圖的事件分發(fā)。數(shù)據(jù)綁定庫(Data Binding Library)自動生成將布局中的視圖與data對象綁定所需的類。

數(shù)據(jù)綁定布局文件略有不同,必須以 layout 標(biāo)記開頭,后跟 data 元素和 view 根元素。 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 元素中的變量user描述了可在此布局中使用的屬性。

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

布局中的使用 "@{}" 語法來使用屬性。這里,TextView文本設(shè)置為user變量的 firstName屬性:

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

注意:布局表達(dá)式應(yīng)保持短小且簡單,因為它們不能進(jìn)行單元測試并且IDE支持有限。為了簡化布局表達(dá)式,您可以使用自定義綁定適配器。


Data object(數(shù)據(jù)對象)

假設(shè)有一個普通的實體對象User:

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ù)。在應(yīng)用程序中,通常會讀取一次并且之后不會更改的數(shù)據(jù)。也可以使用具有約定規(guī)則的寫法,例如訪問器方法的用法,如以下示例所示:

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}用于該android:text 屬性的表達(dá)式訪問firstName前一類中的字段和getFirstName()后一類中的 方法?;蛘撸?strong>firstName()如果存在該方法,也可以解決。


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

為每個布局文件生成綁定類。默認(rèn)情況下,類的名稱基于布局文件的名稱,將其轉(zhuǎn)換為Pascal大小寫并向其添加Binding后綴。上面的布局文件名是 activity_main.xml相應(yīng)生成的類 MainActivityBinding。此類包含布局屬性(例如,user變量)到布局視圖的所有綁定,并知道如何為綁定表達(dá)式指定值。建議綁定的推薦方法是在擴(kuò)展布局時執(zhí)行此操作,如圖所示在以下示例中:

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
   User user = new User("Test", "User");
   binding.setUser(user);
}

注意:上面MainActivityBinding是官網(wǎng)中寫法,正確寫法是ActivityMainBinding請讀者自行替換,下同。

在運(yùn)行時,應(yīng)用程序在UI中顯示Test用戶?;蛘?,您可以使用a獲取視圖LayoutInflater,如以下示例所示:

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

如果您在 Fragment, ListView或RecyclerView適配器中使用數(shù)據(jù)綁定,則可能更適合使用 inflate() 綁定類或 DataBindingUtil類的方法,如以下代碼示例所示:

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


表達(dá)語言

一般特征

表達(dá)式語言看起來很像托管代碼中的表達(dá)式。您可以在表達(dá)式語言中使用以下運(yùn)算符和關(guān)鍵字:

  • 基本運(yùn)算 + - / * %
  • 字符串連接 +
  • 邏輯表達(dá)式 && ||
  • 二進(jìn)制 & | ^
  • 一元運(yùn)算符 + - ! ~
  • 移位 >> >>> <<
  • 比較運(yùn)算 == > < >= <=
  • instanceof
  • 分組 ()
  • 字面值 - 字符,字符串,數(shù)字, null
  • 強(qiáng)轉(zhuǎn)
  • 方法調(diào)用
  • 屬性訪問
  • 數(shù)組訪問 []
  • 三元運(yùn)算符 ?:

舉例:

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


缺少操作

您可以在托管代碼中使用的表達(dá)式語法中缺少以下操作:

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


空結(jié)合運(yùn)算符

空合并運(yùn)算符(??)如果不是,則選擇左操作數(shù),如果是null 前者,則選擇右操作數(shù)null。

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

這在功能上等價于:

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


屬性引用

表達(dá)式可以使用以下格式引用類中的屬性,這對于字段,getter和ObservableField 對象是相同的 :

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


空指針異常避免

生成的數(shù)據(jù)綁定代碼會自動檢查null值并避免空指針異常。例如,在表達(dá)式中@{user.name},如果 user為null,user.name則為其分配其默認(rèn)值null。如果您引用user.ageage,其中age是類型int,則數(shù)據(jù)綁定使用默認(rèn)值0。


集合

可以使用[]運(yùn)算符訪問普通集合,例如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]}"

注意:您還可以使用object.key表示法引用map中的值。例如,@{map[key]}在上面的示例中可以替換為 @{map.key}。


字符串文字

您可以使用單引號括起屬性值,這允許在表達(dá)式中使用雙引號,如以下示例所示:

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

也可以使用雙引號來包圍屬性值。這時,字符串文字應(yīng)該用后引號括起來`:

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


資源

可以使用以下語法訪問表達(dá)式中的資源:

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

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

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

當(dāng)復(fù)數(shù)采用多個參數(shù)時,應(yīng)傳遞所有參數(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


事件處理

數(shù)據(jù)綁定允許您編寫從視圖調(diào)度的表達(dá)式處理事件(例如,onClick()方法)。事件屬性名稱由監(jiān)聽器方法的名稱確定,但有一些例外。例如,View.OnClickListener有一個方法onClick(),所以這個事件的屬性是android:onClick。

對于click事件,有一些專門的事件處理程序需要除android:onClick避免沖突之外的屬性。您可以使用以下屬性來避免這些類型的沖突:

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

您可以使用以下機(jī)制來處理事件:

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

方法引用

事件可以直接綁定到處理程序方法,類似于android:onClick 可以分配給活動中的方法的方式。與View onClick屬性相比的一個主要優(yōu)點(diǎn) 是表達(dá)式在編譯時處理,因此如果該方法不存在或其簽名不正確,則會收到編譯時錯誤。

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

要將事件分配給其處理程序,請使用普通綁定表達(dá)式,其值為要調(diào)用的方法名稱。例如,請考慮以下示例數(shù)據(jù)對象:

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

綁定表達(dá)式可以將View的單擊監(jiān)聽器分配給 onClickFriend()方法,如下所示:

<?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)聽器對象中方法的簽名完全匹配。

監(jiān)聽器綁定

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

在方法引用中,方法的參數(shù)必須與事件監(jiān)聽器的參數(shù)匹配。在監(jiān)聽器綁定中,只有您的返回值必須與監(jiān)聽器的預(yù)期返回值匹配(除非它期望void)。例如,請考慮以下具有該onSaveClick() 方法的Presenter 類:

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

然后,您可以將click事件綁定到onSaveClick()方法,如下所示:

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

在表達(dá)式中使用回調(diào)時,數(shù)據(jù)綁定會自動創(chuàng)建必要的監(jiān)聽器并注冊事件。當(dāng)View觸發(fā)事件時,數(shù)據(jù)綁定會計算給定的表達(dá)式。與常規(guī)綁定表達(dá)式一樣,在計算這些監(jiān)聽器表達(dá)式時,仍然可以獲得數(shù)據(jù)綁定的null和線程安全性。

在上面的示例中,我們尚未定義view傳遞給的參數(shù)onClick(View)。監(jiān)聽器綁定為監(jiān)聽器參數(shù)提供了兩種選擇:忽略方法的所有參數(shù),也可以命名所有參數(shù)。如果為參數(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á)式也必須返回相同類型的值。例如,如果要監(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á)式,則數(shù)據(jù)綁定將返回該類型的默認(rèn)值。例如:引用類型為null對于參考, int為0,boolean為false等。

如果需要使用帶斷言的表達(dá)式(例如:三元運(yùn)算),則可以使用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)該在從監(jiān)聽器表達(dá)式調(diào)用的回調(diào)方法中實現(xiàn)任何業(yè)務(wù)邏輯。


Imports、variables和includes

數(shù)據(jù)綁定庫提供導(dǎo)入,變量和包含等功能。導(dǎo)入使布局文件中的類很容易引用。變量允許您描述可用于綁定表達(dá)式的屬性。包括讓您在整個應(yīng)用中重復(fù)使用復(fù)雜的布局。


Imports

導(dǎo)入允許您輕松引用布局文件中的類,就像在托管代碼中一樣。import可以在data 元素內(nèi)使用零個或多個元素。以下代碼示例將View類導(dǎo)入布局文件:

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

導(dǎo)入的View類,可以在綁定表達(dá)式中引用它。以下示例顯示如何引用View類的常量VISIBLEGONE

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


Type aliases(類型別名)

當(dāng)存在類名沖突時,可以將其中一個類重命名為別名。以下示例將包中的View類 重命名com.example.real.estateVista

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

這樣,可以用Vista來引用com.example.real.estate.View,而View引用android.view.View


導(dǎo)入其他類

導(dǎo)入的類型可以用作變量和表達(dá)式中的類型引用。以下示例顯示UserList用作變量的類型:

<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ùn)行。您的應(yīng)用程序仍在編譯,您可以通過在變量定義中使用完全限定名稱來解決IDE問題。

您還可以使用導(dǎo)入的類型來進(jìn)行強(qiáng)轉(zhuǎn)。以下示例將connection屬性強(qiáng)制類型轉(zhuǎn)換為User

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

在表達(dá)式中引用靜態(tài)字段和方法時,也可以使用導(dǎo)入的類型。以下代碼導(dǎo)入MyStringUtils類并引用其capitalize方法:

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


variables(變量)

您可以variable在元素內(nèi)使用多個元素data。每個 variable元素都描述了一個屬性,該屬性可以在布局中設(shè)置,以便在布局文件中的綁定表達(dá)式中使用。下面的示例聲明的user,image和note變量:

<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)Observableobservable collection,則應(yīng)該在類型中反射。如果變量的基類或接口未實現(xiàn)Observable接口,則不會觀察變量。

當(dāng)存在用于各種配置的不同布局文件(例如,橫向或縱向)時,組合變量。這些布局文件之間不得存在沖突的變量定義。

生成的綁定類對于每個描述的變量都有一個setter和getter。調(diào)用setter之前,變量采用托管代碼默認(rèn)值 -- 引用類型為null,int為0,boolean為false等。

特殊變量context,可由綁定表達(dá)式生成。context的值是由根View的getContext()方法獲取的Context對象。context變量由具有該名稱的顯式變量聲明覆蓋。


includes(包含)

通過使用app命名空間和屬性中的變量名,變量可以從包含的布局傳遞到包含的布局綁定中。以下示例顯示user了name.xml和 contact.xml布局文件中包含的變量:

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

數(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><!-- Doesn't work -->
       <include layout="@layout/name"
           bind:user="@{user}"/>
       <include layout="@layout/contact"
           bind:user="@{user}"/>
   </merge>
</layout>




參考
[1] https://developer.android.com/topic/libraries/data-binding/
[2] https://developer.android.com/topic/libraries/data-binding/expressions

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

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,351評論 25 708
  • 用兩張圖告訴你,為什么你的 App 會卡頓? - Android - 掘金 Cover 有什么料? 從這篇文章中你...
    hw1212閱讀 14,119評論 2 59
  • 江南煙雨前塵事, 鶴城自譜決絕詩, 十年東西隔山水, 此生不復(fù)往來時; 不念不忘無所思, 不恨不愛不枉世, 寒月獨(dú)...
    關(guān)馨仁閱讀 548評論 0 3
  • 多用類型變量,少用#define預(yù)處理指令 若不算公開某個常量,則應(yīng)該定義在該常量的實現(xiàn)文件里: static c...
    程序馬閱讀 228評論 0 0
  • 國家之間交流最重要的媒介是語言,它是人們交流思想的首選工具,學(xué)習(xí)不同國家的語言是展開合作的基礎(chǔ)和前提?!耙粠б宦贰?..
    功夫漢語KFCC閱讀 59評論 0 1

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