您现在的位置是:首页 >技术交流 >【Jetpack】DataBinding 架构组件 ⑤ ( 数据模型与视图双向绑定 | BaseObservable 实现双向绑定 | ObservableField 实现双向绑定 )网站首页技术交流

【Jetpack】DataBinding 架构组件 ⑤ ( 数据模型与视图双向绑定 | BaseObservable 实现双向绑定 | ObservableField 实现双向绑定 )

韩曙亮 2023-05-22 09:01:49
简介【Jetpack】DataBinding 架构组件 ⑤ ( 数据模型与视图双向绑定 | BaseObservable 实现双向绑定 | ObservableField 实现双向绑定 )





一、数据模型 Model 与视图 View 双向绑定




1、数据模型 Model 与视图 View 的单向绑定


在之前的博客中 ,

数据模型 Model 中的 指定 Field 字段 绑定到 View 视图中的组件 ,

在实际案例中 , 将 Student 类中的 String 类型的 name 字段绑定到了 布局文件中的 TextView 组件中 ,

当 Student#name 字段发生了改变 ,

对应的 TextView 组件中显示的内容也发生了相应的修改 ;


上述绑定方式可以理解为 单向绑定 ,

因为 TextView 组件不能修改 , 只能显示 ,

数据模型中的字段修改 , 可以改变 TextView 显示的内容 ;

TextView 组件不能发起对数据模型的修改 ;

在这里插入图片描述


2、由单向绑定引出双向绑定


如果 绑定的 数据模型 对应的组件是 EditText 文本框 ,

EditText 组件的内容可以自行进行修改 ,

数据模型 可以发起对 EditText 组件的修改 ,

同时 EditText 也可以发起对数据模型的修改 ,

那么就会出现一个 双向绑定 的问题 ;

在这里插入图片描述





二、BaseObservable 实现数据模型 Model 与视图 View 双向绑定



示例代码 : https://download.csdn.net/download/han1202012/87702558


1、启用 DataBinding


使用 DataBinding 前 , 必须启用数据绑定 ,

Module 下的 build.gradle 构建脚本 中 , 在 " android / defaultConfig " 层级 , 配置

        // 启用 DataBinding
        dataBinding {
            enabled = true
        }

内容 , 即可启用 数据绑定 ;


完整层级的代码如下 :

android {
    namespace 'kim.hsl.databinding_demo'
    compileSdk 32

    defaultConfig {
        applicationId "kim.hsl.databinding_demo"
        minSdk 21
        targetSdk 32

        // 启用 DataBinding
        dataBinding {
            enabled = true
        }
    }
}

2、导入 kotlin-kapt 插件


凡是 在 Kotlin 中使用到注解的情况下 , 都需要导入 kotlin-kapt 插件 ;

在 Module 下的 build.gradle 构建脚本中 , 导入 kotlin-kapt 插件 ;

plugins {
    id 'kotlin-kapt'
}

3、数据模型类


数据类中 , 主要 封装 数据模型 ;

package kim.hsl.databinding_demo

class Student(var name: String, var age: Int) {
}

4、BaseObservable 实现双向绑定 ( 本博客的核心重点 ) ★


实现 数据 与 视图 的双向绑定类 , 需要继承 BaseObservable 类 ;


class StudentViewModel: BaseObservable {
}

在该类中 , 需要 维护一个 数据类对象 , 如下在 次构造函数 中传入 ;

    lateinit var student: Student

    constructor() {
        this.student = Student("Tom", 18)
    }

实现一个 getXxx 函数 , 使用 @Bindable 注解修饰该函数 ,

同时 在 DataBinding 布局中 , 为 EditText 组件设置值时 , 也使用该函数设置值 ;

设置了 @Bindable 注解 , 只要 student 对象中的 name 发生了变化 , 绑定的组件中的内容就会发生变化 ;

    /**
     * 只要 student 对象中的 name 发生了变化
     * 绑定的组件中的内容就会发生变化
     */
    @Bindable
    fun getStudentName(): String {
        return student.name
    }

如果要实现 通过 EditText 修改 数据模型 的效果 , 需要再实现一个 setXxx 函数 ,

该函数需要与之前的 使用 @Bindable 注解修饰的 getXxx 函数对应 , Xxx 必须是一样的 ;

修改后需要调用 notifyPropertyChanged(BR.xxx) 通知数据模型进行变更 ;

    /**
     * 只要绑定的 EditText 组件内容发生变化
     * 就会自动调用该函数 修改 student 对象中的 name 字段
     */
    fun setStudentName(name: String): Unit {
        // 修改后的字符串不为空 且与之前的值不同 才更新数据模型数据
        if (name != null && !(name == student.name)) {
            student.name = name
            Log.i("StudentViewModel", "setStudentName : ${name}")

            // BR 是编译时自动生成的字段
            // studentName 对应的是 上面 被 @Bindable 修饰的 getStudentName 函数
            notifyPropertyChanged(BR.studentName)
        }
    }

BR 类是 BaseObservable 子类中由 @Bindable 注解修饰的函数生成 ;

BR 类生成位置在 appuildgeneratedsourcekaptdebugkimhsldatabinding_demoBR.java ;

在这里插入图片描述


BaseObservable 类源码如下 :

package kim.hsl.databinding_demo

import android.util.Log
import android.view.View
import androidx.databinding.BaseObservable
import androidx.databinding.Bindable

class StudentViewModel: BaseObservable {

    lateinit var student: Student

    constructor() {
        this.student = Student("Tom", 18)
    }

    /**
     * 只要 student 对象中的 name 发生了变化
     * 绑定的组件中的内容就会发生变化
     */
    @Bindable
    fun getStudentName(): String {
        return student.name
    }

    /**
     * 只要绑定的 EditText 组件内容发生变化
     * 就会自动调用该函数 修改 student 对象中的 name 字段
     */
    fun setStudentName(name: String): Unit {
        // 修改后的字符串不为空 且与之前的值不同 才更新数据模型数据
        if (name != null && !(name == student.name)) {
            student.name = name
            Log.i("StudentViewModel", "setStudentName : ${name}")

            // BR 是编译时自动生成的字段
            // studentName 对应的是 上面 被 @Bindable 修饰的 getStudentName 函数
            notifyPropertyChanged(BR.studentName)
        }
    }
}

5、布局文件设置 ( 重点 )


在 DataBinding 布局文件中 , 需要 在 " data / variable " 标签中 , 引入 StudentViewModel 类型的对象 ;

在位 EditText 组件赋值时 , 需要使用 android:text="@={student.studentName}" 进行赋值 , 注意值为 @={student.studentName} , 比之前的数据绑定多了一个等号 ;


布局代码示例 :

<?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="student"
            type="kim.hsl.databinding_demo.StudentViewModel" />
    </data>

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

        <EditText
            android:id="@+id/imageView"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:text="@={student.studentName}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"/>

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

6、Activity 组件代码 ( 重点 )


在 Activity 组件中 , 向布局中设置的对象类型是 StudentViewModel 类型的 , 不是 Student 类型的 ;

package kim.hsl.databinding_demo

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import kim.hsl.databinding_demo.databinding.ActivityMainBinding

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

        // 设置布局文件
        // 布局文件是 activity_main.xml
        // 该类名称生成规则是 布局文件名称 + Binding
        var activityMainBinding: ActivityMainBinding =
            DataBindingUtil.setContentView(this, R.layout.activity_main)

        // 为布局 设置 数据
        activityMainBinding.student = StudentViewModel()
    }
}

7、执行结果


执行后显示 :

在这里插入图片描述

逐个字母删除 Tom , 然后输入 Jack ;

最终打印如下日志 :

setStudentName : To
setStudentName : T
setStudentName : 
setStudentName : Jack

在这里插入图片描述

在这里插入图片描述





三、ObservableField 实现数据模型 Model 与视图 View 双向绑定 ( 本博客的核心重点 ) ★



示例代码 :


ObservableField 实现数据模型 Model 与视图 View 双向绑定

BaseObservable 实现数据模型 Model 与视图 View 双向绑定

进行对比 ,

除了 StudentViewModel 之外 , 其它代码都一样 ;

重点介绍 StudentViewModel 类 ;


将数据模型类 Student , 定义为 ObservableField 的泛型类 ;

    lateinit var studentObservableField: ObservableField<Student>

在构造函数中 , 创建 Student 对象 , 将其设置到 ObservableField<Student> 对象中 ;

    constructor() {
        var student: Student = Student("Tom", 18)
        studentObservableField = ObservableField()
        studentObservableField.set(student)
    }

定义 getStudentName() 函数 , 获取 ObservableField<Student> 对象中的 Student 对象的 name 属性 ;

    fun getStudentName(): String? {
        return studentObservableField.get()?.name
    }

定义 setStudentName() 函数 , 设置 ObservableField<Student> 对象中的 Student 对象的 name 属性 ;

    fun setStudentName(name: String): Unit {
        studentObservableField.get()?.name = name
        Log.i("StudentViewModel", "setStudentName : ${name}")
    }

完整代码如下 :

package kim.hsl.databinding_demo

import android.util.Log
import androidx.databinding.ObservableField

class StudentViewModel {
    lateinit var studentObservableField: ObservableField<Student>

    constructor() {
        var student: Student = Student("Tom", 18)
        studentObservableField = ObservableField()
        studentObservableField.set(student)
    }

    fun getStudentName(): String? {
        return studentObservableField.get()?.name
    }

    fun setStudentName(name: String): Unit {
        studentObservableField.get()?.name = name
        Log.i("StudentViewModel", "setStudentName : ${name}")
    }
}

执行上述代码 , 也能实现与 BaseObservable 双向绑定相同的效果 ;
在这里插入图片描述

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。