前面的几种文章分析了DataBinding单向数据绑定的原理,今天来看看双向数据绑定是怎么回事。
我们知道单向绑定是在数据发生变化的时候能够通知到UI,让数据的变化能够及时反应到UI上;而双向绑定则是不仅要让数据的变化能够反馈到UI上,而且还要让UI的变化也能够反馈到数据上,前面已经分析了数据的变化如何反馈到UI上,所以这篇文章就只分析UI的变化是如何反馈到数据上。
为了方便说明,我们使用如下的UI进行演示:

界面下方有个格式化时间,它是一个TextView,这里要做的就是在点击该控件的时候把显示内容更新为当前时间,这个操作就对应到UI变化,此时会把当前时间保存到相应的LiveData中(也就是UI变化反馈到数据中)。接下来主要分三步来说明如何实现该效果。
在单向数据绑定中我们按如下方式使用:
单向绑定表达式为:@{viewModel.second},而在双向绑定中按如下方式使用:
双向绑定表达式为:@={viewModel.time},多了个“=”号,同时在生存的相关xml中也有所不同:
false true
相应的
单向绑定中数据变化会通知到UI,使用到的是观察者模式;以LiveData为例,就是在LiveData变化的时候会执行相应的绑定表达式。
而在双向绑定中,则需要监听UI变化,使用的则是事件或者控制提供的机制监听UI变化,以这里的TextView为例。
package com.zfang.databindingstudy.widgetimport android.content.Context
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatTextView
import com.zfang.databindingstudy.binds.AppTextBinds
import java.text.SimpleDateFormat
import java.util.*class MyAppText(ctx: Context, attr: AttributeSet): AppCompatTextView(ctx, attr) {private var timeDate: Date? = nullfun timeChange(time: Date): Boolean {if (null == timeDate) {return true}return timeDate!! != time}private fun setTime(time: String) {text = time}fun setTime(timeDate: Date) {this.timeDate = timeDatesetTime(AppTextBinds.formate(timeDate))}fun getTime() = timeDate!!
}
这是一个自定义TextView用于显示格式化时间,其中的timeChange方法用于判断时间是否有变化,如果有变化再更新显示时间(否则会引起无限循环)。
相应的BindAdapter如下:
package com.zfang.databindingstudy.bindsimport android.util.Log
import androidx.databinding.*
import com.zfang.databindingstudy.widget.MyAppText
import java.text.SimpleDateFormat
import java.util.*
import kotlin.reflect.KClass
//@BindingMethods(
// BindingMethod(type = MyAppText::class, attribute = "app:time", method = "setFormattedTime")
//)
class AppTextBinds {companion object {private val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())fun getDate(timeStr: String) = timeStr.apply {Log.e("zfang", "dateStr = ${this}")formatter.parse(this)}fun formate(date: Date) = formatter.format(date)@BindingAdapter("app:time")@JvmStatic fun setTime(view: MyAppText, newValue: Date) {Log.e("zfang", "setTime")// Important to break potential infinite loops.val timeStr = formatter.format(newValue)if (view.timeChange(newValue)) {view.setTime(newValue)}}/*** 双向绑定调用的方法(UI变化 -> 从UI获取数据)*/@InverseBindingAdapter(attribute = "app:time")@JvmStatic fun getTime(view: MyAppText) : Date {Log.e("zfang", "getTime")return view.getTime()}/*** 设置双向绑定调用时机*/@BindingAdapter("app:timeAttrChanged")@JvmStatic fun setListeners(view: MyAppText, attrChange: InverseBindingListener) {Log.e("zfang", "on UI change")view.apply {setOnClickListener {text = formate(Date())attrChange.onChange()}}}}
}
其中的setListeners用于建立双向绑定的监听,它是由DataBinding调用的,在该方法中设置了View的点击监听,同时更新了UI上的显示数据,接着调用InverseBindingListener的onChange,该方法会更新相应的LiveData数据。
相应的LiveData如下:
package com.zfang.databindingstudy.moduleimport androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import java.util.*class SimpleViewModel: ViewModel() {private val _first = MutableLiveData("Alice")private val _second = MutableLiveData("Bob")val first: LiveData = _firstval second: LiveData = _secondvar time :MutableLiveData = MutableLiveData(Date())set(date) {if (field == date) {return}field = date}
}
数据流路径为:onClick -> InverseBindingListener.onChange -> 设置LiveData time的值,需要注意的是此时time的变化会导致requestRebind的调用,重而更新UI,此时需要判断数据是否发生变化再设置相应的LiveData数据,否则会产生死循环。
接着上面说到的InverseBindingListener.onChange调用,其实现如下:
// Inverse Binding Event Handlersprivate InverseBindingListener mboundView3timeAttrChanged = new InverseBindingListener() {@Overridepublic void onChange() {// Inverse of viewModel.time.getValue()// is viewModel.time.setValue((Date) callbackArg_0)//上面定义的方法,获取时间Date callbackArg_0 = AppTextBinds.getTime(mboundView3);// localize variables for thread safety// viewModel.time.getValue()Date viewModelTimeGetValue = null;// viewModelSimpleViewModel viewModel = mViewModel;// viewModel.timeMutableLiveData viewModelTime = null;// viewModel != nullboolean viewModelJavaLangObjectNull = false;// viewModel.time != nullboolean viewModelTimeJavaLangObjectNull = false;viewModelJavaLangObjectNull = (viewModel) != (null);if (viewModelJavaLangObjectNull) {viewModelTime = viewModel.getTime();viewModelTimeJavaLangObjectNull = (viewModelTime) != (null);if (viewModelTimeJavaLangObjectNull) {//设置UI数据到LiveData中viewModelTime.setValue(((Date) (callbackArg_0)));}}}};
上面带注释的两处即是更新了相应数据中的值(数据是从UI中获取,在当前场景中也就是TextView)。当然这里的代码是DataBinding生存的,我们需要做的是实现AppTextBinds 中SetListener方法,监听UI的变化并回调InverseBindingListener.onChange,这样就实现UI的变化反馈到数据中。
需要了解单向绑定的可以点这里(DataBinding原理----单向数据绑定(3)),源代码在这里