【Android Architecture】Data Binding向導之布局和綁定表達式

表達式語言允許您編寫處理由視圖調度的事件的表達式。 Data Binding庫會自動生成將布局中的視圖與數據對象綁定所需的類。

數據綁定布局文件略有不同,它們以layout的根標記開始,后跟一個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>

數據中的user變量描述了可以在此布局中使用的屬性。

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

使用@ {}語法將布局內的表達式寫入屬性屬性中。 在這里,TextView文本設置為user變量的firstName屬性:

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

注意:布局表達式應保持小而簡單,因為它們不能進行單元測試并且對IDE的支持有限。 為了簡化布局表達,您可以使用自定義binding adatpers。

數據對象


現在讓我們假設您有一個描述User實體的普通對象:


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

這種類型的對象具有永不更改的數據。 在應用程序中,通常只讀取一次數據,此后再也不會更改。 也可以使用遵循一組約定的對象,例如Java中訪問器方法的用法,如以下示例所示:

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的角度來看,這兩個類是等效的。 用于android:text屬性的表達式@ {user.firstName}用于訪問前一類中的firstName字段和后一類中的getFirstName()方法。 或者,如果該方法存在,它也解析為firstName()

綁定數據


為每個布局文件生成一個綁定類。 默認情況下,類的名稱基于布局文件的名稱,將其轉換為Pascal大小寫并向其添加Binding后綴。 上面的布局文件名是activity_main.xml,因此相應的生成類是ActivityMainBinding。 此類包含從布局屬性(例如user變量)到布局視圖的所有綁定,并且知道如何為綁定表達式分配值。創建綁定的推薦方法是在使布局inflate的同時進行綁定,例如 如以下示例所示:

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

在運行時,該應用程序在UI中顯示Test用戶。 或者,您可以使用LayoutInflater獲取視圖,如以下示例所示:

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

如果在FragmentListViewRecyclerView適配器中使用數據綁定項,則可能更喜歡使用綁定類或DataBindingUtil類的inflate()方法,如以下代碼示例所示:

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

表達式語言


通用特性

表達式語言看起來很像托管代碼中的表達式。 您可以在表達式語言中使用以下運算符和關鍵字:

  • 數學運算符 + - / * %
  • 字符串連接符 +
  • 邏輯運算符 && ||
  • 二元運算符 & | ^
  • 一元運算符 + - ! ~
  • 移位運算符 >> >>> <<
  • 比較運算符 == > < >= <= (Note that < needs to be escaped as <)
  • instanceof
  • Grouping ()
  • Literals - character, String, numeric, null
  • Cast
  • Method calls
  • Field access
  • Array access []
  • 三元運算符 ?:
    例如:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age > 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
Missing operations

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

  • this
  • supper
  • new
  • 顯式泛型調用
Null合并操作符

null合并操作符(??)如果不為null,則選擇左操作數;如果前一個為null,則選擇右操作數。

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

功能上等同于

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

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

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

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

集合

為了方便起見,可以使用[]運算符訪問常見的集合,例如array,lists,sparse lists和map。

<data>
    <import type="android.util.SparseArray"/>
    <import type="java.util.Map"/>
    <import type="java.util.List"/>
    <variable name="list" type="List&lt;String>"/>
    <variable name="sparse" type="SparseArray&lt;String>"/>
    <variable name="map" type="Map&lt;String, String>"/>
    <variable name="index" type="int"/>
    <variable name="key" type="String"/>
</data>
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"

注意:為了使XML在語法上正確,必須對<字符進行轉義。 例如:您必須編寫List<String>而不是List <String>。

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

String literals

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

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

也可以使用雙引號將屬性值引起來。 這樣做時,字符串文字應該用反引號`引起來:

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

您可以使用以下語法訪問表達式中的資源:

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

格式字符串和復數形式可以通過提供參數來評估:

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

當復數帶有多個參數時,應傳遞所有參數:

  Have an orange
  Have %d oranges

android:text="@{@plurals/orange(orangeCount, orangeCount)}"

某些資源需要顯式類型評估,如下表所示:

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

Data Binding使您可以編寫從視圖分派的表達式處理事件(例如onClick()方法)。 事件屬性名稱由偵聽器方法的名稱確定,但有一些例外。 例如,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

您可以使用以下機制來處理事件:

  • Method references: 在表達式中,可以引用符合偵聽器方法簽名的方法。 當表達式對方法引用求值時,數據綁定將方法引用和所有者對象包裝在偵聽器中,然后在目標視圖上設置該偵聽器。 如果表達式的計算結果為null,則數據綁定不會創建偵聽器,而是設置一個null偵聽器。

  • Listener bindings: 這些是事件發生時評估的lambda表達式。 數據綁定始終創建一個偵聽器,并在視圖上進行設置。 調度事件后,偵聽器將評估lambda表達式。

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

方法引用和偵聽器綁定之間的主要區別在于,實際的偵聽器實現是在綁定數據時創建的,而不是在事件觸發時創建的。 如果您希望在事件發生時評估表達式,則應使用listener binding

要將事件分配給其處理程序,請使用常規綁定表達式,該值是要調用的方法名稱。 例如,考慮以下示例布局數據對象:

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

綁定表達式可以將視圖的點擊偵聽器分配給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>

注意:表達式中方法的簽名必須與偵聽器對象中方法的簽名完全匹配。

Listener bindings
Listener bindings是事件發生時運行的綁定表達式。 它們類似于方法引用,但是它們使您可以運行任意數據綁定表達式。 適用于Gradle 2.0版及更高版本的Android Gradle插件提供了此功能。

在方法引用中,方法的參數必須與事件偵聽器的參數匹配。 在偵聽器綁定中,只有您的返回值必須與偵聽器的期望返回值匹配(除非期望返回void)。 例如,考慮以下具有onSaveClick()方法的演示者類:

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>

在表達式中使用回調時,data binding將自動創建必要的偵聽器并將其注冊為事件。 當視圖觸發事件時,數據綁定將評估給定的表達式。 像在常規綁定表達式中一樣,在評估這些偵聽器表達式時,您仍然可以獲得數據綁定的null和線程安全性。

在上面的示例中,我們尚未定義傳遞給onClick(View)的view參數。 Listener binding為偵聽器參數提供了兩種選擇:您可以忽略該方法的所有參數,也可以全部命名。 如果您想命名參數,則可以在表達式中使用它們。 例如,上面的表達式可以編寫如下:

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

或者,如果要在表達式中使用參數,則可以按以下方式工作:

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

您可以使用具有多個參數的lambda表達式:

public class Presenter {
    public void onCompletedChanged(Task task, boolean completed){}
}
<CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content"
      android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />

如果您正在監聽的事件返回的值的類型不為void,則表達式也必須返回相同類型的值。 例如,如果您想監聽長按事件,則表達式應返回一個布爾值。

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

如果由于null對象而無法對表達式求值,則數據綁定將返回該類型的默認值。 例如,對于引用類型,為null,對于int,為0,對于布爾值,為false,等等。

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

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

Avoid complex listeners
偵聽器表達式非常強大,可以使您的代碼非常易于閱讀。 另一方面,包含復雜表達式的偵聽器使您的布局難以閱讀和維護。 這些表達式應該簡單到將可用數據從UI傳遞到回調方法一樣簡單。 您應該在從偵聽器表達式調用的回調方法內實現任何業務邏輯。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,769評論 2 372

推薦閱讀更多精彩內容