Android 数据绑定库

578 阅读8分钟

数据绑定库是一种支持库,借助该库我们可以使用声明性格式(而非程序化地)将布局中的界面组件绑定到应用中的数据源。

要将应用配置为使用数据绑定,请在应用模块的 build.gradle文件中添加dataBinding元素,如以下示例所示:

android {
    dataBinding {
        enabled = true
    }
}

布局和绑定表达式

借助表达式语言可以编写表达式来处理视图分派的事件。数据绑定库会自动生成将布局中的视图与我们的数据对象绑定所需的类。 数据绑定布局文件略有不同,以根标记layout开头,后跟data元素和view根元素。此视图元素是非绑定布局文件中的根。

在Android Studio为我们生成的布局文件中我们可以选择根节点,然后使用Alt+Enter组合键,快速将该布局转换为数据绑定布局。

转化后的布局文件:

<?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>

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <TextView
            android:id="@+id/helloWorld"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!"
            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节点中就是我们定义布局变量的地方。

布局变量用于编写布局表达式。布局中的表达式使用“@{}”语法写入特性属性中,例如:

在我们的实际使用中,更多的会将一个数据对象绑定。比如我们创建一个数据类User:

data class User(var userName: String, var age: Int)

接下来我们在布局文件中定义变量:

    <data>

        <variable
            name="user"
            type="com.qiushangge.easylearn.data.User" />
    </data>

使用:

android:text="@{user.userName}"

绑定数据

系统会为每个布局文件生成一个绑定类。默认情况下,类名称基于布局文件的名称,它会转换为 Pascal 大小写形式并在末尾添加 Binding 后缀。以上布局文件名为activity_main.xml,因此生成的对应类为ActivityMainBinding。此类包含从布局属性(例如,user变量)到布局视图的所有绑定,并且知道如何为绑定表达式指定值。建议的绑定创建方法是在扩充布局时创建,如以下示例所示:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding: ActivityMainBinding =
            DataBindingUtil.setContentView(this, R.layout.activity_main)

        binding.user = User("Android Kotlin开发", 10)
    }
}

其中DataBindingUtil是用来创建数据绑定的工具类。

在xml中我们也可以使用表达式,比如:

android:text='@{user.age > 10 ? user.userName : @string/app_name}'

事件处理

通过数据绑定,我们可以编写从视图分派的表达式处理事件,处理事件的机制可以使用如下两种之一:

  • 方法引用:在表达式中,可以引用符合监听器方法签名的方法。当表达式求值结果为方法引用时,数据绑定会将方法引用和所有者对象封装到监听器中,并在目标视图上。

事件可以直接绑定到处理脚本方法,类似于为Activity中的方法指定android:onClick的方式。与ViewonClick特性相比,一个主要优点是表达式在编译时进行处理,因此,如果该方法不存在或其签名不正确,则会收到编译时错误。

方法引用和监听器绑定之间的主要区别在于实际监听器实现是在绑定数据时创建的,而不是在事件触发时创建的。如果希望在事件发生时对表达式求值,则应使用监听器绑定。要将事件分配给其处理脚本,请使用常规绑定表达式,并以要调用的方法名称作为值。

我们给User类添加一个方法:

class User(var userName: String, var age: Int) {
    fun logPrint(view: View): Unit {
        Log.i("数据绑定", "数据绑定点击时间处理")
    }
}

然后在xml中设置view的点击事件:

android:onClick="@{user::logPrint}"

表达式中的方法签名必须与监听器对象中的方法签名完全一致,这里我们去掉logPrint的参数:

  fun logPrint(): Unit {
        Log.i("数据绑定", "数据绑定点击时间处理")
    }

然后运行APP,此时编译器会给出如下提示:

Listener class android.view.View.OnClickListener with method onClick did not match signature of any method user::logPrint
  • 监听器绑定:这些是在事件发生时进行求值的lambda表达式。数据绑定始终会创建一个要在视图上设置的监听器。事件被分派后,监听器会对lambda表达式进行求值。

监听器绑定是在事件发生时运行的绑定表达式。它们类似于方法引用,但允许运行任意数据绑定表达式。此功能适用于 Gradle 2.0 版及更高版本的 Android Gradle 插件。

在方法引用中,方法的参数必须与事件监听器的参数匹配。在监听器绑定中,返回值必须与监听器的预期返回值相匹配(预期返回值无效除外)。

同样是User类,我们先不传入view参数,此时在xml中可以这样使用:

 android:onClick="@{()->user.logPrint()}"

和方法引用不同,监听器绑定提供两个监听器参数选项:

  • 忽略方法的所有参数

比如我们最常见的onClick方法,其定义如下:

  • 命名所有参数

也就是两个极端,要么我们忽略所有的参数,要么我们必须传入事件需要的参数,如果传入了参数,那么我们就可以直接使用:

android:onClick="@{(view)->user.logPrint(view)}"

不管是哪种方式,的表达式也必须返回和监听事件相同类型的值。比如长按事件:

返回值为布尔类型,那么我们的函数返回值也必须是布尔类型。

当然了,我们可以在lambda 表达式中使用多个参数:

class User() {
    var name: String = "Android Kotlin开发"
    var age: Int = 0
    fun userDate(name: String, age: Int): Unit {
        this.name = name
        this.age = age
        Log.i("数据绑定", "$name;$age")
    }
}

 android:onClick='@{()->user.userDate("android",10)}'

导入、变量和包含

数据绑定库提供了诸如导入、变量和包含等功能。通过导入功能,可以轻松地在布局文件中引用类。通过变量功能,可以描述可在绑定表达式中使用的属性。通过包含功能,可以在整个应用中重复使用复杂的布局。

通过导入功能,可以轻松地在布局文件中引用类,就像在托管代码中一样。可以在data元素使用多个import元素,也可以不使用。以下代码示例将 View 类导入到布局文件中:

<import type="android.view.View"/>

此时我们可以直接在布局文件中使用View相关的属性,比如我们最常见的:

 android:visibility="@{user.age > 10 ? View.VISIBLE : View.INVISIBLE }"

这里我们需要在提以下类型别名,当类名有冲突时,其中一个类可使用别名重命名,使用方式是在import中添加alias属性指定别名:

<import type="android.view.View" alias="androidView"/>

使用可观察数据对象

前面我们将数据进行了绑定,如果我们的数据内容发生变化,此时ui是不会随着数据的变化而进行自动更新。

可观察性是指一个对象将其数据变化通知给其他对象的能力。通过数据绑定库,可以让对象、字段或集合变为可观察。通过数据绑定,数据对象可在其数据发生更改时通知其他对象,即监听器。可观察类有三种不同类型:对象、字段和集合。当其中一个可观察数据对象绑定到界面并且该数据对象的属性发生更改时,界面会自动更新。

如果我们只需要观察个别的字段,那么我们可以将该变量设置为可观察字段,创建可观察字段可以通过以下的特定于基元的类:

  • ObservableBoolean

  • ObservableByte

  • ObservableChar

  • ObservableShort

  • ObservableInt

  • ObservableLong

  • ObservableFloat

  • ObservableDouble

  • ObservableParcelable

可观察字段是具有单个字段的自包含可观察对象,如果要访问字段值,请使用 set() 和 get() 访问器方法,还是用User类来举例:

class User() {
    var name = ObservableField<String>()
    var age = ObservableInt()

    fun update(): Unit {
        age.set(age.get() + 1)
    }
}

修改布局文件:

        android:onClick='@{()->user.update()}'
        android:text='@{user.name + user.age}'

运行app:

如果我们需要使用动态结构来保存数据,可使用ObservableArrayMap或者ObservableArrayList,具体的我们就不在多说了。

可观察对象 我们最常用的还是可观察对象,通过实现 Observable 接口的类允许注册监听器,以便它们接收有关可观察对象的属性更改的通知。 Observable 接口具有添加和移除监听器的机制,但何时发送通知则必须由开发者决定。为便于开发,数据绑定库提供了用于实现监听器注册机制的 BaseObservable 类。实现 BaseObservable 的数据类负责在属性更改时发出通知。具体操作过程是向 getter 分配 Bindable 注释,然后在 setter 中调用 notifyPropertyChanged() 方法


class User() : BaseObservable() {

    @Bindable
    var name: String = ""
        set(value) {
            field = value
            notifyPropertyChanged(BR.name)
        }

    @Bindable
    var age: Int = 0
        set(value) {
            field = value
            notifyPropertyChanged(BR.age)
        }

    fun update() {
        age++
    }
}

数据绑定在模块包中生成一个名为 BR 的类,该类包含用于数据绑定的资源的 ID。在编译期间,Bindable 注释会在 BR 类文件中生成一个条目。