表达式语言允许您编写处理由视图调度的事件的表达式。 Data Binding
库会自动生成将布局中的视图与数据对象绑定所需的类。
数据绑定布局文件略有不同,它们以layout
的根标记开始,后跟一个data
元素和一个view
根元素。 此视图元素是您的根将位于非绑定布局文件中的元素。 以下代码显示了一个示例布局文件:
数据中的user变量描述了可以在此布局中使用的属性。
使用@ {}
语法将布局内的表达式写入属性属性中。 在这里,TextView
文本设置为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。
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"
注意:为了使XML在语法上正确,必须对<字符进行转义。 例如:您必须编写List
而不是List 。
您还可以使用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()
方法,如下所示:
注意:表达式中方法的签名必须与侦听器对象中方法的签名完全匹配。
Listener bindings
Listener bindings
是事件发生时运行的绑定表达式。 它们类似于方法引用,但是它们使您可以运行任意数据绑定表达式。 适用于Gradle 2.0版及更高版本的Android Gradle插件提供了此功能。
在方法引用中,方法的参数必须与事件侦听器的参数匹配。 在侦听器绑定中,只有您的返回值必须与侦听器的期望返回值匹配(除非期望返回void)。 例如,考虑以下具有onSaveClick()
方法的演示者类:
public class Presenter {
public void onSaveClick(Task task){}
}
然后,可以将click事件绑定到onSaveClick()
方法,如下所示:
在表达式中使用回调时,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){}
}
如果您正在监听的事件返回的值的类型不为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传递到回调方法一样简单。 您应该在从侦听器表达式调用的回调方法内实现任何业务逻辑。