運算式語言可讓您編寫運算式,用於處理檢視畫面分派的事件。資料繫結程式庫會自動產生將版面配置中的檢視畫面與資料物件繫結所需的類別。
資料繫結版面配置檔案略有不同,且開頭為 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());
如要在 Fragment
、ListView
或 RecyclerView
轉接程式中使用資料繫結項目,建議使用繫結類別的 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 鍵:
>> >>> <<
- 比較:
== > < >= <=
(<
必須逸出為<
) 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<String>"/>
<variable name="sparse" type="SparseArray<String>"/>
<variable name="map" type="Map<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:
</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: -> presenter.onSaveClick(task)}" />
</LinearLayout>
</layout>
在運算式中使用回呼時,資料繫結會自動建立必要的事件監聽器,並為事件註冊該事件監聽器。當檢視區塊觸發事件時,資料繫結會評估指定的運算式。與一般繫結運算式一樣,在評估這些事件監聽器運算式時,您會取得資料繫結的空值和執行緒安全性。
在上述範例中,並未定義傳遞至 onClick(View)
的 view
參數。事件監聽器繫結會提供兩種事件監聽器參數選項:您可以忽略所有方法的參數,或為所有參數命名。如果您要為參數命名,可以在運算式中使用。例如,您可以編寫上述運算式,如下所示:
android: -> presenter.onSaveClick(task)}"
如果您想在運算式中使用參數,可以按照以下方式進行:
Kotlin
class Presenter { fun onSaveClick(view: View, task: Task){} }
Java
public class Presenter { public void onSaveClick(View view, Task task){} }
android: -> 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: 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: -> presenter.onLongClick(theView, task)}"
如果因 null
物件而無法評估運算式,資料繫結會傳回該類型的預設值,例如 null
代表參照類型、0
為 int
,或 false
為 boolean
。
如果您需要使用包含述詞 (例如三元) 的運算式,可以使用 void
做為符號:
android: -> v.isVisible() ? doSomething() : void}"
避免使用複雜的事件監聽器
事件監聽器運算式非常強大,可讓程式碼更易於閱讀。另一方面,包含複雜運算式的事件監聽器會讓版面配置更難以讀取及維護。請將運算式簡化為簡單,就像將可用資料從 UI 傳遞至回呼方法一樣。在回呼方法中實作您從事件監聽器運算式叫用的任何商業邏輯。
匯入、變數及包括
資料繫結程式庫提供匯入、變數和納入等功能。匯入功能可讓您在版面配置檔案中輕鬆參照類別。變數可讓您說明可用於繫結運算式的屬性。包括讓您在應用程式中重複使用複雜的版面配置。
匯入
匯入功能可讓您參照版面配置檔案中的類別,例如在代管程式碼中參照類別。您可以在 data
元素中使用零個或多個 import
元素。以下程式碼範例會將 View
類別匯入版面配置檔案:
<data>
<import type="android.view.View"/>
</data>
匯入 View
類別可讓您從繫結運算式參照該類別。以下範例說明如何參照 View
類別的 VISIBLE
和 GONE
常數:
<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.View
和 View
,來參照版面配置檔案中的 android.view.View
。
匯入其他課程
您可以將匯入的類型設為變數和運算式中的類型參照。以下範例顯示做為變數類型的 User
和 List
:
<data>
<import type="com.example.User"/>
<import type="java.util.List"/>
<variable name="user" type="User"/>
<variable name="userList" type="List<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
元素都說明一種可在版面配置上設定的屬性,該屬性可用於版面配置檔案內的繫結運算式。以下範例宣告 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>
系統會在編譯期間檢查變數類型,因此如果變數實作了 Observable
或是可觀測的集合,就必須反映在類型中。如果變數是未實作 Observable
介面的基礎類別或介面,則「不會」觀察變數。
如果各種設定 (例如橫向或直向) 會有不同的版面配置檔案,則系統會合併這些變數。這些版面配置檔案之間不得有衝突的變數定義。
產生的繫結類別針對每個描述的變數都有一個 setter 和 getter。變數會採用預設的代管程式碼值,直到呼叫 setter 為止。如果是參照類型,則為 null
、適用於 int
的 0
、boolean
為 false
等。
系統會產生名為 context
的特殊變數,以便視需要在繫結運算式中使用。context
的值是根層級檢視畫面 getContext()
方法中的 Context
物件。context
變數是由使用該名稱的明確變數宣告覆寫。
包含
您可以使用應用程式命名空間和屬性中的變數名稱,將變數從包含的版面配置傳入納入版面配置的繫結中。以下範例顯示 name.xml
和 contact.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>