DataBinding的使用六

DataBinding的使用六,第1张

文章目录
  • 一、前言
  • 二、属性的定义
  • 三、使用自定义特性的双向数据绑定
  • 四、转换器
  • 五、其余内容
    • 使用双向数据绑定的无限循环
    • 双向特性
  • 六、参考链接

一、前言

本篇记录下关于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


<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


<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;
    }

}

如果实在想使用kotlin写代码,需要使用以下方式

需要注意的是 java.lang.String需要直接写,不需要联想,联想功能出现的列表里面没有String类。其字符串类型不能直接使用" 内容 "的方式生成,需要使用以下方式
java.lang.String("新的日期")

Converter.kt

object Converter {
    @InverseMethod("stringToDate")
    @JvmStatic fun dateToString(
        view: AppCompatEditText, oldValue: Long,
        value: Long
    ): java.lang.String {
        // Converts long to String.
        Log.e("YM"," dateToString类型转换")
        return java.lang.String("新的日期")
    }

    @JvmStatic fun stringToDater(
        view: AppCompatEditText, oldValue: Long,
        value: java.lang.String
    ): Long {
        // Converts String to long.
        Log.e("YM"," stringToDate类型转换")
        return 0L
    }
}

activity_main.xml


<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。无需引入额外的库,直接使用对象就行

使用双向数据绑定的无限循环

使用双向数据绑定时,请注意不要引入无限循环。当用户更改特性时,系统会调用使用 @InverseBindingAdapter 注释的方法,并且该值将分配给后备属性。继而调用使用 @BindingAdapter 注释的方法,从而触发对使用 @InverseBindingAdapter 注释的方法的另一个调用,依此类推。

因此,通过比较使用 @BindingAdapter 注释的方法中的新值和旧值,可以打破可能出现的无限循环。

双向特性

当您使用下表中的特性时,该平台提供对双向数据绑定的内置支持。有关平台如何提供此类支持的详细信息,请参阅相应绑定适配器的实现:

特性绑定适配器
AdapterViewandroid:selectedItemPosition
android:selection
AdapterViewBindingAdapter
CalendarViewandroid:dateCalendarViewBindingAdapter
CompoundButtonandroid:checkedCompoundButtonBindingAdapter
DatePickerandroid:year
android:month
android:day
DatePickerBindingAdapter
NumberPickerandroid:valueNumberPickerBindingAdapter
RadioButtonandroid:checkedButtonRadioGroupBindingAdapter
RatingBarandroid:ratingRatingBarBindingAdapter
SeekBarandroid:progressSeekBarBindingAdapter
TabHostandroid:currentTabTabHostBindingAdapter
TextViewandroid:textTextViewBindingAdapter
TimePickerandroid:hour
android:minute
TimePickerBindingAdapter
六、参考链接
  1. 双向数据绑定  |  Android 开发者  |  Android Developers

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

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/729246.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-27
下一篇 2022-04-27

发表评论

登录后才能评论

评论列表(0条)

保存