在之前的博客中 ,
将 数据模型 Model 中的 指定 Field 字段 绑定到 View 视图中的组件 ,
在实际案例中 , 将 Student 类中的 String 类型的 name 字段绑定到了 布局文件中的 TextView 组件中 ,
当 Student#name 字段发生了改变 ,
对应的 TextView 组件中显示的内容也发生了相应的修改 ;
上述绑定方式可以理解为 单向绑定 ,
因为 TextView 组件不能修改 , 只能显示 ,
数据模型中的字段修改 , 可以改变 TextView 显示的内容 ;
TextView 组件不能发起对数据模型的修改 ;
如果 绑定的 数据模型 对应的组件是 EditText 文本框 ,
EditText 组件的内容可以自行进行修改 ,
数据模型 可以发起对 EditText 组件的修改 ,
同时 EditText 也可以发起对数据模型的修改 ,
那么就会出现一个 双向绑定 的问题 ;
示例代码 : https://download.csdn.net/download/han1202012/87702558
使用 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
}
}
}
凡是 在 Kotlin 中使用到注解的情况下 , 都需要导入 kotlin-kapt 插件 ;
在 Module 下的 build.gradle 构建脚本中 , 导入 kotlin-kapt 插件 ;
plugins {
id 'kotlin-kapt'
}
数据类中 , 主要 封装 数据模型 ;
package kim.hsl.databinding_demo
class Student(var name: String, var age: Int) {
}
实现 数据 与 视图 的双向绑定类 , 需要继承 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 类生成位置在 app\build\generated\source\kapt\debug\kim\hsl\databinding_demo\BR.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)
}
}
}
在 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>
在 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()
}
}
执行后显示 :
逐个字母删除 Tom , 然后输入 Jack ;
最终打印如下日志 :
setStudentName : To
setStudentName : T
setStudentName :
setStudentName : Jack
示例代码 :
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 双向绑定相同的效果 ;