表達式語言允許您編寫處理由視圖調度的事件的表達式。 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());
如果在Fragment
,ListView
或RecyclerView
適配器中使用數據綁定項,則可能更喜歡使用綁定類或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.age
,age
是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<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]}"
注意:為了使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傳遞到回調方法一樣簡單。 您應該在從偵聽器表達式調用的回調方法內實現任何業務邏輯。