编译环境
要将应用配置为使用数据绑定,请在应用模块的build.gradle
文件中添加dataBinding
元素:
android {
...
dataBinding {
enabled = true
}
}
使用数据绑定库
数据绑定的布局文件以根标记layout
开头,后跟data
元素和view
根元素:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="user"
type="com.jetpackdemo.User" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.name}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
在data
中使用了包名为com.jetpackdemo
的User
文件,并为此对象设置了name:user
。
在TextView
中:android:text="@{user.name}"
表示为TextView
设置user
的name
属性值。
完成布局文件后,系统会为每一个布局文件生成一个绑定类,默认情况下,类名基于布局文件的名称,将xml
文件的名称转换为驼峰大小写,并在末尾添加Binding
一词。
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.user = User("hello")
}
如果在Fragment
、ListView
和RecyclerView
适配器中使用数据绑定,则可以使用DataBindingUtil
的inflate
方法:
val listItemBinding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false)
布局表达式
可以在布局文件的使用一下运算符和关键字:
- 算术运算符
+ - * / %
- 字符串连接运算符
+
- 逻辑运算符
&& ||
- 二元运算符
& | ^
- 一元运算符
+ - ! ~
- 移位运算符
>> >>> <<
- 比较运算符
== > < >= <=
(<需要转义为<
) - instanceof
- 分组运算符
()
- 字面量运算符-字符、字符串、数字、
null
- 类型转换
- 方法调用
- 字段访问
- 数组访问
[ ]
- 三元运算符
?:
缺少的布局表达式
this
super
new
- 显式泛型调用
Null
合并运算符
android:text="@{user.name ?? user.sex}"
如果左边运算不为null
则选择左边运算,否则选择右边运算。
属性引用
android:text="@{user.name}"
避免出现Null
指针异常
生成的数据绑定代码会自动检查有没有null
值并避免出现null
指针异常。如果在表达式中String
为null
则分配默认值null
。
集合表达式
为了方便访问,可使用[ ]
运算符访问常见集合,例如数组、列表、稀疏列表和映射。
<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]}"
在集合中,必须使用转义<字符。例如:不要写成
List<String>
形式,而是必须写成List<String>
。
获取map中的值
android:text='@{map.get("name")}'
事件处理
通过数据绑定,您可以编写从视图分派的表达式处理事件(例如:onClick()
方法)。事件名称由监听器方法的名称确定。
可以使用以下机制处理事件:
方法引用:在表达式中,您可以引用符合监听器方法签名的方法,当表达式求值结果为方法引用时,数据绑定会将方法引用和所有者对象封装到监听器中,并在目标视图上设置该监听器。如果表达式的求值结果为
null
,则数据绑定不会创建监听器,而是设置null
监听器。
监听器绑定:这些是在事件发生时进行求值的lambda
表达式。数据绑定始终会创建一个要在视图上设置的监听器。事件被分派后,监听器会对lambda
表达式进行求值。
方法引用
一个主要优点是表达式在编译时进行处理,因此,如果该方法不存在或其签名不正确,则会收到编译时错误。
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>
方法引用中的签名必须和方法中的签名保持一致,并且参数也保持一致,不能自定义参数。
监听器绑定
监听器绑定是在事件发生时运行的绑定表达式。它们类似于方法引用,但允许您运行任意数据绑定表达式。此功能适用于 Gradle 2.0 版及更高版本的 Android Gradle 插件。
在方法引用中,方法的参数必须与事件监听器的参数匹配。在监听器绑定中,只有您的返回值必须与监听器的预期返回值相匹配(预期返回值无效除外)。
class Presenter {
fun 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>
如果监听的事件返回类型不是void的值,那么表达式也必须返回相同类型的值(比如长按事件返回值是boolean)。
fun onSaveClick(task: Task): Boolean{}
从以上说明可以看出,方法引用更适合简单的事件处理,并且方法不需要自定义参数。监听器则更适合复杂的事件处理,方法可以自定义参数。
避免使用复杂的监听器
监听器表达式功能非常强大,可以使您的代码非常易于阅读。另一方面,包含复杂表达式的监听器会使您的布局难以阅读和维护。这些表达式应该像将可用数据从界面传递到回调方法一样简单。您应该在从监听器表达式调用的回调方法中实现任何业务逻辑。
导入、变量和包含
导入
通过导入功能,您可以轻松地在布局文件中引用类,就像在托管代码中一样。您可以在 data
元素使用多个 import
元素,也可以不使用。以下代码示例将View
类导入到布局文件中:
<data>
<import type="android.view.View"/>
</data>
上述方法中导入了View
类,可以引用View
的属性,比如设置显示:
android:visibility="@{user.man? View.VISIBLE : View.GONE}"
类型别名
当类名有冲突时,其中一个类可使用别名重命名。以下示例将com.example.real.estate
软件包中的View
类重命名为Vista
:
<import type="android.view.View"/>
<import type="com.example.real.estate.View"
alias="Vista"/>
导入其他类
<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"/>
包含
通过使用应用命名空间和特性中的变量名称,变量可以从包含的布局传递到被包含布局的绑定。以下示例展示了来自 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 作为 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>