这种表达式语言(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())
复制代码
若是是在 Fragment
、 ListView
、 RecyclerView
中使用 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 经常使用特性
表达式语言看起来很像代码里面的表达式。你能够在表达式中使用以下的操做符和关键字:
+ - / * %
+
&& ||
& | ^
+ - ! ~
>> >>> <<
== > < >= <=
(注:<
运算符须要写成 <
)instance of
()
[]
?:
举例:
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.age
,age
的类型是 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<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>
。
访问 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。
Data Binding
提供了诸如 imports
、 variables
、 includes
等特性。imports
用于导入所须要的类,方便引用;variable
用于定义一个变量,方便在绑定表达式中使用。includes
使咱们能够复用布局。
下面这个例子展现了导入 View
这个类到布局文件中:
<data>
<import type="android.view.View"/>
</data>
复制代码
导入的目的就是为了方便在绑定表达式中使用。下面的例子展现了在表达式中引用 View
类的两个常量 VISIBLE
和 GOEN
:
<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.*
下面的类会自动导入。
变量的声明用于在绑定表达式中使用,以下所示,声明了 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>
复制代码
自动生成的绑定类,包含了这些变量的 get 和 set 方法。这些变量都会有默认值,引用类型默认值是 null
,int 类型默认值是 0,布尔类型默认值是 false。
变量能够传递给 include
布局,以下所示,user
变量传递给了 name.xml
和 contact.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>
复制代码