0
点赞
收藏
分享

微信扫一扫

DataBinding的使用六

sullay 2022-04-24 阅读 44

文章目录

一、前言

本篇记录下关于DataBinding双向绑定的问题。双向绑定的意思是Modle的数据变化会引起UI刷新,UI的刷新也会反向引起Modle的改变。

二、属性的定义

以下是简单的示例

ObservableUserModel.kt

class ObservableUserModel : ViewModel(), Observable {
    private val callbacks: PropertyChangeRegistry = PropertyChangeRegistry()

    var isCheck = false

    @Bindable
    fun getRememberMe(): Boolean {
        return isCheck
    }

    fun setRememberMe(value: Boolean) {
        // Avoids infinite loops.
        if (isCheck != value) {
            isCheck = value

            // React to the change.
//            saveData()
            Log.e("YM--","---->rememberMe更改后的值:$value")
            // Notify observers of a new value.
            notifyPropertyChanged(BR.rememberMe)
        }
    }

    private var state = false

    var timerRunning: Boolean
        @Bindable get() {
            return state
        }
        set(value) {
            // These methods take care of calling notifyPropertyChanged()
            Log.e("YM--","---->timerRunning更改后的值:$value")
            state = value
            notifyPropertyChanged(BR.timerRunning)
        }

    @get:Bindable
    var newCheck = false
    set(value) {
        field = value
        Log.e("YM--","---->newCheck更改后的值:$value")
//        notifyPropertyChanged(BR.newCheck)
    }

    @Bindable
    var newCheck2 = false
    set(value) {
        field = value
        Log.e("YM--","---->newCheck2更改后的值:$value")
    }

    fun updateName(newName: String) {
        name = newName
        notifyPropertyChanged(BR.name)
    }



    override fun addOnPropertyChangedCallback(
        callback: Observable.OnPropertyChangedCallback
    ) {
        callbacks.add(callback)
    }

    override fun removeOnPropertyChangedCallback(
        callback: Observable.OnPropertyChangedCallback
    ) {
        callbacks.remove(callback)
    }

    /**
     * Notifies observers that all properties of this instance have changed.
     */
    fun notifyChange() {
        callbacks.notifyCallbacks(this, 0, null)
    }

    /**
     * Notifies observers that a specific property has changed. The getter for the
     * property that changes should be marked with the @Bindable annotation to
     * generate a field in the BR class to be used as the fieldId parameter.
     *
     * @param fieldId The generated BR id for the Bindable field.
     */
    fun notifyPropertyChanged(fieldId: Int) {
        callbacks.notifyCallbacks(this, fieldId, null)
    }
}

activity_main.xml

<?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:bind="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <variable
            name="observableModel"
            type="com.example.myapplication.ObservableUserModel" />
    </data>

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

        <androidx.appcompat.widget.AppCompatCheckBox
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="选择"
            android:checked="@={observableModel.newCheck2}"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="@+id/update_content"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"/>

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

以上ObservableUserModel中凡是包含Bindable定义的属性,均可使用双向绑定,在set函数里面可以监听数据改变并执行下一步操作。里面的notifyPropertyChanged()函数视情况调用,比如该值改变后触发其它属性的UI刷新。

三、使用自定义特性的双向数据绑定

之前讲过自定义属性的用法和双向绑定的基本用法。这里将两者进行结合起来进行使用。

InverseObservableUserModel.kt

class InverseObservableUserModel : ViewModel(), Observable {
    private val callbacks: PropertyChangeRegistry = PropertyChangeRegistry()

    @Bindable
    var inverseContent = "默认值Inverse"

    fun updateInverseContent(newValue: String) {
        inverseContent = newValue
        notifyPropertyChanged(BR.inverseContent)
    }


    override fun addOnPropertyChangedCallback(
        callback: Observable.OnPropertyChangedCallback
    ) {
        callbacks.add(callback)
    }

    override fun removeOnPropertyChangedCallback(
        callback: Observable.OnPropertyChangedCallback
    ) {
        callbacks.remove(callback)
    }

    /**
     * Notifies observers that all properties of this instance have changed.
     */
    fun notifyChange() {
        callbacks.notifyCallbacks(this, 0, null)
    }

    /**
     * Notifies observers that a specific property has changed. The getter for the
     * property that changes should be marked with the @Bindable annotation to
     * generate a field in the BR class to be used as the fieldId parameter.
     *
     * @param fieldId The generated BR id for the Bindable field.
     */
    fun notifyPropertyChanged(fieldId: Int) {
        callbacks.notifyCallbacks(this, fieldId, null)
    }
}

InverseBindAdapter.kt

//双向绑定的自定义属性拓展
object InverseBindAdapter {
    @BindingAdapter("time")
    @JvmStatic fun setTime(view: AppCompatEditText, newValue: String) {
        // Important to break potential infinite loops.
        if (view.text?.toString() != newValue) {
            view.setText(newValue)
        }
    }

    //该函数会在使用InverseBindingListener进行调用
    // 而且在使用时候绑定的名字必须为timeAttrChanged,命名规则用AttrChanged做后缀,除非使用event进行指定,
    // 例如  @InverseBindingAdapter(attribute = "time", event = "timeAttrChanged")
    // InverseBindingAdapter必须与InverseBindingListener配套使用,不可单独使用
    // 如果不使用双向绑定的话,如"@={inverseObservableModel.inverseContent}"形式的话则不会调用该函数
    @InverseBindingAdapter(attribute = "time", event = "timeAttrChanged")
    @JvmStatic fun getTime(view: AppCompatEditText) : String {
        Log.e("YM","--->接收返回值")
        return view.text?.toString() ?: ""
    }

    @BindingAdapter("timeAttrChanged") 
    @JvmStatic fun setListeners(
        view: AppCompatEditText,
        attrChange: InverseBindingListener
    ) {
        // Set a listener for click, focus, touch, etc.
        view.onFocusChangeListener = View.OnFocusChangeListener { focusedView, hasFocus ->
            val textView = focusedView as TextView
            Log.e("YM","--->焦点更改:$hasFocus")
            if (hasFocus) {
                // Delete contents of the EditText if the focus entered.
                textView.text = ""
            } else {
                // If the focus left, update the listener
                attrChange.onChange()
            }
        }
    }

}

activity_main.xml

<?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:bind="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <variable
            name="inverseObservableModel"
            type="com.example.myapplication.InverseObservableUserModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
       
        <androidx.appcompat.widget.AppCompatEditText
            android:id="@+id/input"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toTop="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            time="@={inverseObservableModel.inverseContent}"/>

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

四、转换器

有时候使用的数据需要根据情况进行转换,跟前文使用的转换器需求一样,不过代码略有差异,之前使用的转换器为@BindingConversion,现在使用@InverseMethod进行转换,代码如下

InverseObservableUserModel.kt

class InverseObservableUserModel : ViewModel(), Observable {
    private val callbacks: PropertyChangeRegistry = PropertyChangeRegistry()

    @Bindable
    var inverseContent = "默认值Inverse"

    @Bindable
    var inverseDate = Date().time

    fun updateInverseContent(newValue: String) {
        inverseContent = newValue
        notifyPropertyChanged(BR.inverseContent)
    }


    override fun addOnPropertyChangedCallback(
        callback: Observable.OnPropertyChangedCallback
    ) {
        callbacks.add(callback)
    }

    override fun removeOnPropertyChangedCallback(
        callback: Observable.OnPropertyChangedCallback
    ) {
        callbacks.remove(callback)
    }

    /**
     * Notifies observers that all properties of this instance have changed.
     */
    fun notifyChange() {
        callbacks.notifyCallbacks(this, 0, null)
    }

    /**
     * Notifies observers that a specific property has changed. The getter for the
     * property that changes should be marked with the @Bindable annotation to
     * generate a field in the BR class to be used as the fieldId parameter.
     *
     * @param fieldId The generated BR id for the Bindable field.
     */
    fun notifyPropertyChanged(fieldId: Int) {
        callbacks.notifyCallbacks(this, fieldId, null)
    }
}

ConverterAdapter.java

这里为什么使用java,是因为DataBinding编译后的代码是Java类型,如果使用kotlinkotlin的基本类型是kotlin的,不是Java的,所以会导致类型不匹配,一直编译不通过

public class ConverterAdapter{    
    //这里需要注意@InverseMethod("stringToDate")中的参数需要创建一个同名的函数,其中入参,返回参数要相互换下位置
    @InverseMethod("stringToDate")
    public static String dateToString(AppCompatEditText editText,long newValue){
        Log.e("YM"," dateToString类型转换");
        return "新的日期";
    }

    public static long stringToDate(AppCompatEditText editText,String newValue){
        Log.e("YM"," stringToDate类型转换");
        return 0;
    }

}

activity_main.xml

<?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:bind="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <import type="com.example.myapplication.ConverterAdapter"/>
        <variable
            name="inverseObservableModel"
            type="com.example.myapplication.InverseObservableUserModel" />
    </data>

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

        <androidx.appcompat.widget.AppCompatEditText
            android:id="@+id/input"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toBottomOf="@+id/update_content"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            time="@={ConverterAdapter.dateToString(input,inverseObservableModel.inverseDate)}"/>

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

五、其余内容

以下内容主要是引用的官方资料,略微注意一下就行,另外就是在xml中是可以使用context属性的,类型为Context。无需引入额外的库,直接使用对象就行

六、参考链接

  1. 双向数据绑定  |  Android 开发者  |  Android Developers

  2. https://github.com/android/databinding-samples

举报

相关推荐

0 条评论