版面配置與繫結的運算式

運算式語言可讓您編寫運算式,用於處理檢視畫面分派的事件。資料繫結程式庫會自動產生將版面配置中的檢視畫面與資料物件繫結所需的類別。

資料繫結版面配置檔案略有不同,且開頭為 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>

data 中的 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}" />

資料物件

假設您有一個描述 User 實體的純物件:

Kotlin

data class User(val firstName: String, val lastName: String)

Java


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

這類物件的資料不會變更。應用程式往往只有讀取一次,之後資料便不會改變。您也可使用遵循一組慣例的物件,例如在 Java 程式設計語言中使用存取子方法,如以下範例所示:

Kotlin

// Not applicable in Kotlin.
data class User(val firstName: String, val lastName: String)

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

從資料繫結的角度來看,這兩個類別是相等的類別。android:text 屬性使用的運算式 @{user.firstName} 會存取舊類別的 firstName 欄位,以及後者類別中的 getFirstName() 方法。如果存在該方法,也會解析為 firstName()

繫結資料

系統會為每個版面配置檔案產生繫結類別。根據預設,類別的名稱取決於版面配置檔案名稱並轉換為 Pascal 大小寫,並已新增 Binding 後置字串。舉例來說,上述版面配置檔案名稱為 activity_main.xml,因此相應的產生繫結類別為 ActivityMainBinding

此類別會保留版面配置屬性中的所有繫結 (例如 user 變數),並套用至版面配置的檢視區塊,且知道如何為繫結運算式指派值。建議您在加載版面配置時建立繫結,如以下範例所示:

Kotlin

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

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

    binding.user = User("Test", "User")
}

Java

@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 取得檢視畫面,如以下範例所示:

Kotlin

val binding: ActivityMainBinding = ActivityMainBinding.inflate(getLayoutInflater())

Java

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

如要在 FragmentListViewRecyclerView 轉接程式中使用資料繫結項目,建議使用繫結類別的 inflate() 方法或 DataBindingUtil 類別,如以下程式碼範例所示:

Kotlin

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

Java

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

運算式語言

常見功能

運算式語言與代管程式碼中類似。您可以透過運算式語言使用下列運算子和關鍵字:

  • 數學:+ - / * %
  • 字串串連:+
  • 邏輯:&& ||
  • 二進位檔:& | ^
  • 一元:+ - ! ~
  • Shift 鍵:>> >>> <<
  • 比較:== > < >= <= (< 必須逸出為 &lt;)
  • instanceof
  • 分組依據:()
  • 常值,例如字元、字串、數字、null
  • 投放
  • 方法呼叫數
  • 欄位存取權
  • 陣列存取:[]
  • 三元運算子:?:

例如:

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

缺少作業

可在代管程式碼中使用的運算式語法缺少以下作業:

  • this
  • super
  • new
  • 明確的一般叫用

空心耦合運算子

空值合作運算子 (??) 不是 null 時,會選擇左運算元;如果前者為 null,則會選擇右側運算元:

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

這在功能上等同於下列函式:

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

資源參考資料

運算式可以使用以下格式參照類別中的屬性,格式與欄位、getter 和 ObservableField 物件相同:

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

避免空值指標例外狀況

產生的資料繫結程式碼會自動檢查 null 值,並避免出現空值指標例外狀況。舉例來說,在運算式 @{user.name} 中,如果 user 為空值,則 user.name 會指派其預設值 null。如果您參照 user.age,其中年齡為 int 類型,則資料繫結會使用預設值 0

查看參考資料

運算式可以使用下列語法,依 ID 參照版面配置中的其他檢視畫面:

android:text="@{exampleText.text}"

在以下範例中,TextView 檢視區塊會參照同一個版面配置中的 EditText 檢視區塊:

<EditText
    android:id="@+id/example_text"
    android:layout_height="wrap_content"
    android:layout_width="match_parent"/>
<TextView
    android:id="@+id/example_output"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{exampleText.text}"/>

珍藏內容

為方便起見,您可以使用 [] 運算子存取陣列、清單、稀疏清單和地圖等常用集合。

<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]}"

您也可以使用 object.key 標記法參照地圖上的值。例如,您可以將上述範例中的 @{map[key]} 替換為 @{map.key}

字串常值

您可以使用單引號括住屬性值,以便在運算式中使用雙引號,如以下範例所示:

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

您也可以透過雙引號括住屬性值。執行此操作時,字串文字必須使用倒引號 ` 括住,如下所示:

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

資源

運算式可以使用下列語法參照應用程式資源:

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

您可以透過提供參數來評估格式字串和複數:

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

您可以傳遞屬性參照檢視畫面參照做為資源參數:

android:text="@{@string/example_resource(user.lastName, exampleText.text)}"

如果複數使用多個參數,請傳送所有參數:


  Have an orange
  Have %d oranges

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

部分資源需要明確的類型評估,如下表所示:

類型 一般參照 運算式參考資料
String[] @array @stringArray
int[] @array @intArray
TypedArray @array @typedArray
Animator @animator @animator
StateListAnimator @animator @stateListAnimator
color int @color @color
ColorStateList @color @colorStateList

事件處理

資料繫結可讓您編寫從檢視畫面分派的運算式處理事件,例如 onClick() 方法。事件屬性名稱由事件監聽器方法的名稱決定,但有幾個例外。舉例來說,View.OnClickListener 具有 onClick() 方法,因此這個事件的屬性為 android:onClick

點擊事件有些特殊事件處理常式需要 android:onClick 以外的屬性,以免發生衝突。您可以使用下列屬性來避免這類衝突:

類別 事件監聽器設定器 屬性
SearchView setOnSearchClickListener(View.OnClickListener) android:onSearchClick
ZoomControls setOnZoomInClickListener(View.OnClickListener) android:onZoomIn
ZoomControls setOnZoomOutClickListener(View.OnClickListener) android:onZoomOut

您可以使用這兩種機制來處理事件 (詳情請參閱後續章節):

  • 方法參照:在運算式中,您可以參照符合事件監聽器方法簽章的方法。運算式評估方法參照時,資料繫結會將方法參照和擁有者物件納入事件監聽器,並在目標檢視中設定該事件監聽器。如果運算式評估為 null,資料繫結不會建立事件監聽器,而是會設定 null 事件監聽器。
  • 事件監聽器繫結:這些是事件發生時評估的 lambda 運算式。資料繫結一律會建立在檢視畫面設定的事件監聽器。分派事件時,事件監聽器會評估 lambda 運算式。

方法參考資料

您可以將事件繫結至處理常式方法,類似在活動中指派 android:onClick 的方法。與 View onClick 屬性相比,有一個優點是在編譯期間處理運算式。因此,如果方法不存在或其簽名不正確,您會收到編譯時間錯誤。

方法參照和事件監聽器繫結的主要差異在於,系統會在資料繫結時建立實際的事件監聽器實作,而非觸發事件時。如果您想在事件發生時評估運算式,請使用「事件監聽器繫結」

如要將事件指派給處理常式,請使用一般繫結運算式,並將值做為呼叫的方法名稱。例如,請思考以下的版面配置資料物件範例:

Kotlin

class MyHandlers {
    fun onClickFriend(view: View) { ... }
}

Java

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>

事件監聽器繫結

事件監聽器繫結是指在事件發生時執行的繫結運算式。做法類似於方法的參照,但可讓您執行任意的資料繫結運算式。這項功能適用於 Gradle 適用的 Android Gradle 外掛程式 2.0 以上版本。

在方法參照中,方法的參數必須與事件監聽器的參數相符。在事件監聽器繫結中,除非回傳值預期是 void,否則只有回傳值必須與事件監聽器的預期傳回值相符。舉例來說,假設下列呈現器類別具有 onSaveClick() 方法:

Kotlin

class Presenter {
    fun onSaveClick(task: Task){}
}

Java

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

您可以將點擊事件繫結至 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>

在運算式中使用回呼時,資料繫結會自動建立必要的事件監聽器,並為事件註冊該事件監聽器。當檢視區塊觸發事件時,資料繫結會評估指定的運算式。與一般繫結運算式一樣,在評估這些事件監聽器運算式時,您會取得資料繫結的空值和執行緒安全性。

在上述範例中,並未定義傳遞至 onClick(View)view 參數。事件監聽器繫結會提供兩種事件監聽器參數選項:您可以忽略所有方法的參數,或為所有參數命名。如果您要為參數命名,可以在運算式中使用。例如,您可以編寫上述運算式,如下所示:

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

如果您想在運算式中使用參數,可以按照以下方式進行:

Kotlin

class Presenter {
    fun onSaveClick(view: View, task: Task){}
}

Java

public class Presenter {
    public void onSaveClick(View view, Task task){}
}

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

您也可以搭配多個參數使用 lambda 運算式:

Kotlin

class Presenter {
    fun onCompletedChanged(task: Task, completed: Boolean){}
}

Java

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 的值,運算式也必須傳回相同類型的值。例如,如果您想要監聽按住 (長按) 事件,運算式必須傳回布林值。

Kotlin

class Presenter {
    fun onLongClick(view: View, task: Task): Boolean { }
}

Java

public class Presenter {
    public boolean onLongClick(View view, Task task) { }
}

android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"

如果因 null 物件而無法評估運算式,資料繫結會傳回該類型的預設值,例如 null 代表參照類型、0int,或 falseboolean

如果您需要使用包含述詞 (例如三元) 的運算式,可以使用 void 做為符號:

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

避免使用複雜的事件監聽器

事件監聽器運算式非常強大,可讓程式碼更易於閱讀。另一方面,包含複雜運算式的事件監聽器會讓版面配置更難以讀取及維護。請將運算式簡化為簡單,就像將可用資料從 UI 傳遞至回呼方法一樣。在回呼方法中實作您從事件監聽器運算式叫用的任何商業邏輯。

匯入、變數及包括

資料繫結程式庫提供匯入、變數和納入等功能。匯入功能可讓您在版面配置檔案中輕鬆參照類別。變數可讓您說明可用於繫結運算式的屬性。包括讓您在應用程式中重複使用複雜的版面配置。

匯入

匯入功能可讓您參照版面配置檔案中的類別,例如在代管程式碼中參照類別。您可以在 data 元素中使用零個或多個 import 元素。以下程式碼範例會將 View 類別匯入版面配置檔案:

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

匯入 View 類別可讓您從繫結運算式參照該類別。以下範例說明如何參照 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}"/>

類型別名

發生類別名稱衝突時,您可以將其中一個類別重新命名為別名。以下範例會將 com.example.real.estate 套件中的 View 類別重新命名為 Vista

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

接著,您可以使用 Vista 參照 com.example.real.estate.ViewView,來參照版面配置檔案中的 android.view.View

匯入其他課程

您可以將匯入的類型設為變數和運算式中的類型參照。以下範例顯示做為變數類型的 UserList

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

您可以使用匯入的類型,投放運算式的一部分。以下範例會將 connection 屬性轉換為 User 的類型:

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

您也可以在運算式中參照靜態欄位和方法時,使用匯入的類型。以下程式碼會匯入 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.*

Variables

您可以在 data 元素中使用多個 variable 元素。每個 variable 元素都說明一種可在版面配置上設定的屬性,該屬性可用於版面配置檔案內的繫結運算式。以下範例宣告 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>

系統會在編譯期間檢查變數類型,因此如果變數實作了 Observable 或是可觀測的集合,就必須反映在類型中。如果變數是未實作 Observable 介面的基礎類別或介面,則「不會」觀察變數。

如果各種設定 (例如橫向或直向) 會有不同的版面配置檔案,則系統會合併這些變數。這些版面配置檔案之間不得有衝突的變數定義。

產生的繫結類別針對每個描述的變數都有一個 setter 和 getter。變數會採用預設的代管程式碼值,直到呼叫 setter 為止。如果是參照類型,則為 null、適用於 int0booleanfalse 等。

系統會產生名為 context 的特殊變數,以便視需要在繫結運算式中使用。context 的值是根層級檢視畫面 getContext() 方法中的 Context 物件。context 變數是由使用該名稱的明確變數宣告覆寫。

包含

您可以使用應用程式命名空間和屬性中的變數名稱,將變數從包含的版面配置傳入納入版面配置的繫結中。以下範例顯示 name.xmlcontact.xml 版面配置檔案中的 user 變數:

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

資料繫結不支援將 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>
   <merge><!-- Doesn't work -->
       <include layout="@layout/name"
           bind:user="@{user}"/>
       <include layout="@layout/contact"
           bind:user="@{user}"/>
   </merge>
</layout>

其他資源

如要進一步瞭解資料繫結,請參閱下列其他資源。

範例

程式碼研究室

網誌文章