Data Binding 系列(三)佈局和綁定表達式

這種表達式語言(expression language)使咱們可使用表達式處理 view 的事件。Data Binding 庫會自動生成綁定類(binding class)用來處理 view 和 data 的綁定關係。php

使用 Data Binding 的佈局文件和傳統的佈局文件稍有不一樣,它的根標籤是 layout,裏面會有一個 data 子標籤和一個根 view 子標籤。這個根 view 子標籤和傳統的佈局文件是同樣的。具體以下所示:java

<?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 變量,將會在綁定表達式中用到。android

<variable name="user" type="com.example.User" />
複製代碼

綁定表達式用於爲屬性賦值,它使用的語法是 @{}。在下面的例子中,TextView 控件的屬性 text ,被賦值爲 user 變量的 firstName 屬性值:express

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

數據源

假設如今有一個描述 User 實體的數據對象:數組

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

這個數據類的成員屬性都是不可變的、是 public 的。它還有另一種寫法,成員屬性是 private 的,而且提供訪問它們的方法:ide

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;
  }
}
複製代碼

從數據綁定的角度來看,上面兩個實體類是等價的。用於給 android:text 屬性賦值的表達式 @{user.firstName} ,會自動讀取前一個類的 firstName 屬性,或者調用後一個類的 getFirstName() 方法。並且,若是 firstName() 方法存在,也會調用這個方法。佈局

可綁定的數據(Binding data

每一個佈局文件都會生成一個對應的綁定類。默認的綁定類的名字是文件名轉爲駝峯寫法並加上後綴 Binding。好比文件名是 activity_main.xml,對應的綁定類名爲 ActivityMainBinding。這個綁定類保存了數據變量和 view 屬性的綁定關係,而且知道如何爲 view 屬性賦值。推薦的方式是在加載佈局的時候建立綁定類,以下所示:post

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val binding: ActivityMainBinding = DataBindingUtil.setContentView(
            this, R.layout.activity_main)

    binding.user = User("Test", "User")
}
複製代碼

另外,還可使用 inflate() 方法建立綁定類:this

val binding: ActivityMainBinding = ActivityMainBinding.inflate(getLayoutInflater())
複製代碼

若是是在 FragmentListViewRecyclerView 中使用 Data Binding,你可能更傾向於使用 inflate() 方法或 DataBindingUtil 類,以下所示:spa

val listItemBinding = ListItemBinding.inflate(layoutInflater, viewGroup, false)
// or
val listItemBinding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false)
複製代碼

表達式語言(expression language

1 經常使用特性

表達式語言看起來很像代碼裏面的表達式。你能夠在表達式中使用以下的操做符和關鍵字:

  • 計算相關運算符 + - / * %
  • 字符鏈接符 +
  • 邏輯運算符 && ||
  • 二元運算符 & | ^
  • 一元運算符 + - ! ~
  • 移位運算符 >> >>> <<
  • 比較運算符 == > < >= <=(注:< 運算符須要寫成 &lt;
  • instance of
  • ()
  • 字符、字符串、數字、null
  • 強轉
  • 方法調用
  • 屬性訪問
  • 數組訪問 []
  • 三元元素符 ?:

舉例:

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

2 不可用操做

下面操做只能在代碼裏使用,不能用於表達式語法:

  • this
  • super
  • new
  • 泛型聲明

3 空結合運算符(??

空結合運算符左邊表達式若是不爲空,使用左邊表達式結果,不然使用右邊表達式結果

android:text="@{user.displayName ?? user.lastName}"
複製代碼

它等價於:

android:text="@{user.displayName != null ? user.displayName : user.lastName}"
複製代碼

4 屬性引用

使用綁定表達式,能夠引用一個類的成員變量、get 方法、ObservableField

android:text="@{user.lastName}"
複製代碼

5 避免空指針異常

生成的綁定類會自動判空從而避免空指針異常。好比,表達式 @{user.name} ,若是 user 是空,user.name 會被賦予默認值 null。若是引用的是 user.ageage 的類型是 int,那麼會使用 0 做爲默認值。

6 集合

經常使用的集合,如數組、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>"/>
    <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,須要將 < 替換爲 &lt;。如 List<String> 應該寫成 List&lt;String>

訪問 map 中的元素,除了可使用 @{map[key]},也可使用 @{map.key}

7 字符串的寫法

可使用單引號包裹屬性,在表達式中使用雙引號,以下所示:

android:text='@{map["firstName"]}'
複製代碼

也可使用雙引號包裹屬性,在表達式中使用單引號,以下所示:

android:text="@{map[`firstName`]}"
複製代碼

8 訪問資源

能夠在表達式中使用以下語法訪問資源:

android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
複製代碼

9 事件處理

Data Binding 可讓咱們在表達式中處理 view 分發的事件,好比 onClick()。屬性的名字取決於監聽器方法的名字,好比,View.OnClickListener 有一個 onClick() 方法,因此對應的屬性名就是 android:onClick

處理 view 事件有兩種方法:

  • 方法引用:在綁定表達式中,能夠引用一個方法處理事件,須要注意的是,這個方法須要和監聽器的方法簽名徹底一致。若是綁定表達式的結果是 null,對應的 view 不會設置監聽器。
  • 監聽器綁定(listener binding):這種方式老是會給對應的 view 設置監聽器,當 view 收到事件時會調用 lambda 表達式。
  • 方法引用

事件能夠和方法直接綁定,這種方式與 android:onClick 能夠和 activity 中一個方法綁定很相似。這種方式的優點是,綁定表達式是在編譯期間處理的,若是方法不存在或者簽名不匹配,會直接報錯。

和監聽器綁定不一樣的是,方法引用的方式會在數據綁定的時候建立監聽器,監聽器綁定則是在收到事件的時候建立監聽器。

示例以下:

class MyHandlers {
    fun onClickFriend(view: View) { ... }
}
複製代碼
<?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 binding

監聽器綁定的方式,只有在收到事件的時候纔會執行。它和方法引用很像,可是它可使用任意的表達式。這個特性在 Gradle 2.0 及之後可使用。

方法引用的方式,要求方法的簽名必須和監聽器的方法簽名一致。可是監聽器綁定的方式,只要求返回值一致便可。例如,假以下面的 Presenter 類有一個 onSaveClick() 方法:

class Presenter {
fun onSaveClick(task: Task){}
}
複製代碼

onSaveClick() 能夠與 android:onClick 綁定,以下所示:

<?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>
複製代碼

在上面這個例子中,咱們沒有定義 view 參數。監聽器綁定提供了兩種選擇:要麼忽略全部參數,要麼顯式寫出全部參數。若是你喜歡寫出參數,以下所示:

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

若是你須要使用這些參數,以下所示:

class Presenter {
    fun onSaveClick(view: View, task: Task){}
}
複製代碼
android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"
複製代碼

同時,也能夠有多個參數,以下所示:

class Presenter {
    fun onCompletedChanged(task: Task, completed: Boolean){}
}
複製代碼
<CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content"
      android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />
複製代碼

此外,若是你監聽的事件返回值不是 void,那麼你的表達式也須要返回相同的返回值。以下所示:

class Presenter {
    fun onLongClick(view: View, task: Task): Boolean { }
}
複製代碼
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"
複製代碼

若是表達式不能正常執行,那麼會返回默認值,引用類型返回 null,int 類型返回 0,布爾類型返回 false。

Imports、variables、includes

Data Binding 提供了諸如 importsvariablesincludes 等特性。imports 用於導入所須要的類,方便引用;variable 用於定義一個變量,方便在綁定表達式中使用。includes 使咱們能夠複用佈局。

  • Imposts

下面這個例子展現了導入 View 這個類到佈局文件中:

<data>
    <import type="android.view.View"/>
</data>
複製代碼

導入的目的就是爲了方便在綁定表達式中使用。下面的例子展現了在表達式中引用 View 類的兩個常量 VISIBLEGOEN

<TextView android:text="@{user.lastName}" android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
複製代碼
    • 類型別名

若是導入的兩個類名字相同,能夠爲其中一個或兩個起別名以區分:

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

注:java.lang.* 下面的類會自動導入。

  • Variables

變量的聲明用於在綁定表達式中使用,以下所示,聲明瞭 userimagenote 三個變量:

<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>
複製代碼

自動生成的綁定類,包含了這些變量的 get 和 set 方法。這些變量都會有默認值,引用類型默認值是 null,int 類型默認值是 0,布爾類型默認值是 false。

  • Includes

變量能夠傳遞給 include 佈局,以下所示,user 變量傳遞給了 name.xmlcontact.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>
複製代碼

Data binding 不支持 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>
複製代碼
相關文章
相關標籤/搜索