DataBinding原理----双向绑定(4)

        前面的几种文章分析了DataBinding单向数据绑定的原理,今天来看看双向数据绑定是怎么回事。

        我们知道单向绑定是在数据发生变化的时候能够通知到UI,让数据的变化能够及时反应到UI上;而双向绑定则是不仅要让数据的变化能够反馈到UI上,而且还要让UI的变化也能够反馈到数据上,前面已经分析了数据的变化如何反馈到UI上,所以这篇文章就只分析UI的变化是如何反馈到数据上。

        为了方便说明,我们使用如下的UI进行演示:

DataBinding原理----双向绑定(4)_第1张图片

         界面下方有个格式化时间,它是一个TextView,这里要做的就是在点击该控件的时候把显示内容更新为当前时间,这个操作就对应到UI变化,此时会把当前时间保存到相应的LiveData中(也就是UI变化反馈到数据中)。接下来主要分三步来说明如何实现该效果。

一、修改绑定表达式

        在单向数据绑定中我们按如下方式使用:

        

        单向绑定表达式为:@{viewModel.second},而在双向绑定中按如下方式使用:

        

        双向绑定表达式为:@={viewModel.time},多了个“=”号,同时在生存的相关xml中也有所不同:

        
            
                
                    
                    false
                    
                
            
            
        
        
            
                
                    
                    true
                    
                
            
            
        

        相应的true标签为true,而单向绑定中为false。

二、监听UI变化

        单向绑定中数据变化会通知到UI,使用到的是观察者模式;以LiveData为例,就是在LiveData变化的时候会执行相应的绑定表达式。

        而在双向绑定中,则需要监听UI变化,使用的则是事件或者控制提供的机制监听UI变化,以这里的TextView为例。

package com.zfang.databindingstudy.widget

import 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? = null
    fun 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 = timeDate
        setTime(AppTextBinds.formate(timeDate))
    }

    fun getTime() = timeDate!!
}

        这是一个自定义TextView用于显示格式化时间,其中的timeChange方法用于判断时间是否有变化,如果有变化再更新显示时间(否则会引起无限循环)。

        相应的BindAdapter如下:

package com.zfang.databindingstudy.binds

import 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.module

import 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 = _first
    val second: LiveData = _second

    var 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 Handlers
    private InverseBindingListener mboundView3timeAttrChanged = new InverseBindingListener() {
        @Override
        public 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;
            // viewModel
            SimpleViewModel viewModel = mViewModel;
            // viewModel.time
            MutableLiveData viewModelTime = null;
            // viewModel != null
            boolean viewModelJavaLangObjectNull = false;
            // viewModel.time != null
            boolean 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)),源代码在这里

你可能感兴趣的:(DataBinding,Android,ui)