【Android】Data Binding 用法总结

本文讲解 Data Binding 基本用法,单向绑定,双向绑定。

官方文档:https://developer.android.google.cn/topic/libraries/data-binding

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

 

注:本文使用 Kotlin 编写。

 

在应用模块的 build.gradle 文件中添加 dataBinding 元素:

android {
  ...
  dataBinding {
      enabled = true
  }
}

 

目录

一、基本用法

二、单向绑定

三、双向绑定


 

一、基本用法

1. 创建一个 XML 布局文件

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

</androidx.constraintlayout.widget.ConstraintLayout>

2. 在父布局最左侧使用键盘快捷键 alt + enter ,就会出现提示,选择 "Convert to data binding layout" 选项

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

    </data>

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

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

3. 新建一个 User 类

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

4. 在布局中绑定 User 类,这样就把 User 对象引入到布局中。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="user_bean"
            type="com.tyhoo.jetpack.databinding.basic.User" />
    </data>

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

    </androidx.constraintlayout.widget.ConstraintLayout>
    
</layout>

5. 完善布局

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

    <data>

        <variable
            name="user_bean"
            type="com.tyhoo.jetpack.databinding.basic.User" />
    </data>

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

        <androidx.constraintlayout.widget.Guideline
            android:id="@+id/guide_vertical"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            app:layout_constraintGuide_percent="0.5" />

        <TextView
            android:id="@+id/tv_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Name"
            android:textSize="24sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toStartOf="@+id/guide_vertical"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/tv_age"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Age"
            android:textSize="24sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="@+id/guide_vertical"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

6. 编写 Activity ,绑定布局

class BasicActivity : AppCompatActivity() {

    lateinit var mBinding: ActivityBasicBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_basic)
    }
}

7. 完善 Activity ,将 User 绑定到布局上

class BasicActivity : AppCompatActivity() {

    lateinit var mBinding: ActivityBasicBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_basic)

        var user = User("Android", 10)

        mBinding.userBean = user
    }
}

8. 在布局上去操作这个绑定

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

    ...

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

        ...

        <TextView
            ...
            android:text="@{user_bean.name}"
            ... />

        <TextView
            ...
            android:text="@{String.valueOf(user_bean.age)}"
            ... />

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

9. 运行程序,发现不需要我们对布局进行赋值,User 对应的值就已经绑定到两个 TextView 上。

10. ActivityBasicBinding 是 Data Binding 根据布局文件 activity_basic 帮我们自动生成的,所以对这个类名进行自定义

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

    <data class="BasicBinding">

        ...
    </data>

    ...

</layout>

11. 回到 BasicActivity ,发现 ActivityBasicBinding 这个类名已经不存在了,使用刚才定义的新类名进行替换

class BasicActivity : AppCompatActivity() {

    lateinit var mBinding: BasicBinding

    ...
}

12. 在布局文件 TextView 的表达式中,可以指定一个 Default ,方便在编写的过程中进行界面预览。

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

    ...

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

        ...

        <TextView
            ...
            android:text="@{user_bean.name,default = Name}"
            ... />

        <TextView
            ...
            android:text="@{String.valueOf(user_bean.age),default=Age}"
            ... />

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

二、单向绑定

在 Activity 中,数据发生变化,会自动显示在当前界面上。

比如说:User 的 name 或 age 变了,设置 User 属性的同时,界面会跟着改变。

1. 继承于 DataBinding 中的 Observable 类

class User : BaseObservable() {}

2. 把 User 里面的 name 和 age 写到 {} 内,因为继承 BaseObservable 我们要重写它的 get 和 set 方法,所以在 Kotlin 中 data 模式就不能在使用了,因为它的 get 和 set 是默认实现。

class User : BaseObservable() {
    var name: String = ""
    var age: Int = 0
}

3. 重写 set ,有三种:

  • set                              默认实现
  • set(value) = ...          相当于等于一个函数值,在 Kotlin 中它也是一个对象
  • set(value) = {...}       相当于重写 set 方法,但是这个 set 方法一定要紧跟当前这个成员变量的下面

我们使用第三种:

class User : BaseObservable() {
    var name: String = ""
        set(value) {
            
        }

    var age: Int = 0
        set(value) {

        }
}

4. 使用 Data Binding 注解来完善 User 

前提:在应用模块的 build.gradle 文件中引用插件 apply plugin: 'kotlin-kapt'

class User : BaseObservable() {

    @Bindable
    var name: String = ""
        set(value) {
            // 给一个默认实现,相当于 Java 中的 this.name = name
            field = value
            // 因为继承了 BaseObservable ,所以要观察 name 值的变化
            notifyPropertyChanged(BR.name)
        }

    @Bindable
    var age: Int = 0
        set(value) {
            // 给一个默认实现,相当于 Java 中的 this.age = age
            field = value
            // 因为继承了 BaseObservable ,所以要观察 age 值的变化
            notifyPropertyChanged(BR.age)
        }
}

notifyChange() 和 notifyPropertyChanged() 区别:

notifyPropertyChanged 存了一个 String 值,相当于它引用了 name 。通知界面 name 这个变量发生改变。

notifyChange 相当于它会通知整个 User 对象进行更新,是全局的,更耗性能。

5. 修改布局文件,追加 Button 按钮

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

    ...

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

        ...


        <Button
            android:id="@+id/btn_1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toStartOf="@+id/guide_vertical"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="@+id/guid_horizontal" />

        <Button
            android:id="@+id/btn_2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="@+id/guide_vertical"
            app:layout_constraintTop_toTopOf="@+id/guid_horizontal" />

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

6. 在 Activity 添加点击事件进行动态更新

class OneWayActivity : AppCompatActivity() {

    lateinit var mBinding: OneWayBinding
    lateinit var mUser: User

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_one_way)
        mUser = User()

        mBinding.userBean = mUser

        btn_1.setOnClickListener {
            mUser.name = "AAA"
            mUser.age = 20
        }

        btn_2.setOnClickListener {
            mUser.name = "BBB"
            mUser.age = 30
        }
    }
}

7. 运行代码,点击 Button 的时候,name 和 age 都会动态更新。

8. 一个问题

Q:如果项目特别大,需要在每个成员变量下面写 notifyPropertyChanged 或 notifyChange 吗?

A:Google 在 Observable 基础之上有封装了一个包装类,叫 ObservableField 。这个类默认实现了 notify 方法。所以也就不需要去写 set 和 get 。

修改 User 代码:

class User : BaseObservable() {

    @Bindable
    var name: ObservableField<String> = ObservableField("")

    @Bindable
    var age: ObservableField<Int> = ObservableField(0)
}

修改 Activity 代码:

class OneWayActivity : AppCompatActivity() {

    ...

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

        btn_1.setOnClickListener {
            mUser.name.set("AAA")
            mUser.age.set(20)
        }

        btn_2.setOnClickListener {
            mUser.name.set("BBB")
            mUser.age.set(30)
        }
    }
}

题外话:

这样写 User 会减少很多代码,但是会有一个问题,如果 User 类从各种地方(如:本地、网络等)序列化的,它不会直接序列化成 ObservableField 类,看一下 ObservableField 源码:

public class ObservableField<T> extends BaseObservableField implements Serializable {}

虽然它实现了 Serializable ,但是序列化过程中的损耗不理想,一层层的封装,序列化过程中会出现一些问题,所以 Data Binding 有利也有弊,在实际项目中有局限性。

三、双向绑定

MVVM 架构模式的精髓,View 改变,数据同时也跟着改变。

Data Binding 最让人惊艳的地方就是它的双向绑定。数据驱动界面,反过来用界面的事件去驱动数据。

1. 数据绑定

1.1 在布局里添加一个 EditText ,将它和 name 进行绑定

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

    ...

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

        ...

        <EditText
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="16dp"
            android:hint="@string/app_name"
            android:text="@={user_bean.name}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

此时 User 的 name 和 EditText 的内容会动态的改变。

1.2 运行代码看效果:

点击 Button ,TextView 会变成 AAA ,然后 EditText 会动态的变成 AAA。 说明界面是根据数据的改变进行改变的。

1.3 在 EditText 上输入内容:

此时数据会根据 EditText 界面上的内容进行改变,然后将数据传递到各处。

2. 事件绑定

在 View 的界面上绑定一个事件,界面的事件会传到 Activity 事件的方式方法上。

例如:

2.1 把 Button 事件提到一个单独的类里:

class Listener(val user: User) {

    fun changeAge() {
        user.age.set(user.age.get() ? .plus(1))
    }
}

2.2 在界面中导入这个 Listener

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

    <data class="TwoWayBinding">

        ...

        <variable
            name="listener"
            type="com.tyhoo.jetpack.databinding.twoway.TwoWayActivity.Listener" />
    </data>

    ...

</layout>

2.3 对界面的 Button 做点击事件

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

    ...

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

        ...

        <Button
            android:id="@+id/btn_2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="@{() -> listener.changeAge()}"
            android:text="Age"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="@+id/guide_vertical"
            app:layout_constraintTop_toTopOf="@+id/guid_horizontal" />

        ...

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

2.4 在 Activity 里设置这个 Listener

class TwoWayActivity : AppCompatActivity() {

    ...

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

        mBinding.listener = Listener(mUser)

        ...
    }

    class Listener(val user: User) {

        fun changeAge() {
            user.age.set(user.age.get()?.plus(1))
        }
    }
}

2.5 运行代码,点击 Button ,可以动态的改变界面数据。

如果本文对你有帮助,请点赞支持!!!

猜你喜欢

转载自blog.csdn.net/cnwutianhao/article/details/107750055