android Databinding 自定View双向绑定

Android Databinding 自定View双向绑定

在使用Databinding的过程中 因为用了自定义View
自定义View在使用单向双向绑定与双向绑定的时候
我们需要去做绑定适配
目前网上关于自定义View双向绑定的文章并不是很多
而且有一些方法随着Databinding的更新已经无效了
现在把自己的方法写出来 授人与渔


话不多说 直接show code (Demo在文未)
这是本文中会用到的自定义View很简单就是一个组合的LinearLayout
两个Button 加一个Edittext 实现个按钮计数功能
我们要做的就是实现这个自定义View的双向绑定
package com.pcf.databindingrecyclerview

import android.content.Context
import android.text.Editable
import android.text.InputType
import android.text.TextWatcher
import android.util.AttributeSet
import android.util.TypedValue
import android.view.Gravity
import android.view.ViewGroup
import android.widget.*
import androidx.databinding.InverseBindingListener
import com.pcf.customize.binding.DensityUtil
import com.pcf.customize.binding.NumberInputFilter
import com.pcf.customize.binding.R


class GoodsCounterView : LinearLayout {
    var listener :InverseBindingListener ?= null
    lateinit var editText: EditText
    lateinit var reduceButton: ImageButton
    lateinit var addButton: ImageButton

    constructor(context: Context?) : super(context)

    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
//        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.goods_Counter)
//        val count = typedArray.getInt(R.styleable.goods_Counter_count, 1)

        setVerticalGravity(HORIZONTAL)
        gravity = Gravity.CENTER_VERTICAL

        val buttonWidth = DensityUtil.dp2px(37.33f)

        reduceButton = ImageButton(context)
        reduceButton.layoutParams = ViewGroup.LayoutParams(buttonWidth.toInt(), buttonWidth.toInt())
        reduceButton.setImageResource(R.drawable.selector_good_reduce)
        reduceButton.background = resources.getDrawable(R.color.gray_fbfbfb)
        reduceButton.isEnabled = false
        val textViewWidth = DensityUtil.dp2px(47.33f)
        editText = EditText(context)
        editText.width = textViewWidth.toInt()
        editText.height = buttonWidth.toInt()
        editText.gravity = Gravity.CENTER
        editText.setTextColor(resources.getColor(R.color.blue_32374a))
        editText.setTextSize(TypedValue.COMPLEX_UNIT_SP, 19f)
        editText.inputType = InputType.TYPE_CLASS_NUMBER
        editText.background = resources.getDrawable(android.R.color.transparent)
//        editText.setText(count.toString())
        editText.addTextChangedListener(object : TextWatcher {
            override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {

            }

            override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {

            }

            override fun afterTextChanged(s: Editable) {
                parseNum()
                //触发反向数据的传递
//                if (mInverseBindingListener != null) {
//                    mInverseBindingListener?.onChange()
//                }
            }
        })
        addButton = ImageButton(context)
        addButton.layoutParams = LayoutParams(buttonWidth.toInt(), buttonWidth.toInt())
        addButton.setImageResource(R.drawable.selector_good_add)
        addButton.background = resources.getDrawable(R.color.gray_fbfbfb)

        addView(reduceButton)
        addView(editText)
        addView(addButton)

        reduceButton.setOnClickListener {
            var num = editText.text.toString().toLong()
            num -= 1
            editText.setText(if (num > 1) num.toString() else "1")
            if (num <= 1) {
                reduceButton.isEnabled = false
            }
        }
        addButton.setOnClickListener {
            var num = editText.text.toString().toLong()
            num += 1
            editText.setText(num.toString())
            reduceButton.isEnabled = true
        }
        editText.filters = arrayOf(NumberInputFilter())
    }

    private fun parseNum() {
        val text = editText.text.toString()
        if (text.isNotEmpty()) {
            var num = text.toLong()
            addButton.isEnabled = true
            reduceButton.isEnabled = num > 1
        } else {
            reduceButton.isEnabled = false
            addButton.isEnabled = false
        }
        listener?.onChange()
    }

    fun setText(text: String) {
        editText.setText(text)
    }

    fun getText(): String {
        return editText.text.toString()
    }
}
这是个bean 很简单 只有一个属性count
但是这里我们用到了 Observable在我们的值发生改变时去通知我们的view,这里我们用到了注解,kotlin使用注解要在App目录下引入
apply plugin: 'kotlin-kapt'
apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

apply plugin: 'kotlin-kapt'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.1"
    defaultConfig {
        applicationId "com.pcf.customize.binding"
        minSdkVersion 15
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    dataBinding {
        enabled true
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'androidx.core:core-ktx:1.0.2'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.2.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

ObservableViewModel 是一个实现Observable接口并继承ViewModel的类
关于ObservableViewModel 看本文 https://blog.csdn.net/qq_29983773/article/details/100656485
package com.pcf.customize.binding

import androidx.databinding.Bindable

class GoodsBean : ObservableViewModel {

    constructor(count: Long) : super() {
        this.count = count
    }
    
    var count: Long = 0
        @Bindable
        set(value) {
            if (value == field) {
                return
            }
            field = value
            notifyPropertyChanged(BR._all)
        }

}
Databinding的基本使用方法我就不多说了
我们直接来看xml
这里我们引入了GoodsBean, 用到了TextView把count绑定到了TextView
//单向绑定的语法
@{}
重点来看GoodsCounterView 这里我们用到了numberOfSets这个自定义属性来实现双向绑定。这个属性在哪里定义的呢。在我的BindingAdapter里面
//双向绑定的语法
@={}
<?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="goods"
           type="com.pcf.customize.binding.GoodsBean" />
    </data>

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

        <com.pcf.customize.binding.GoodsCounterView
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:numberOfSets="@={goods.count}"
            android:gravity="center"/>

        <TextView
            android:layout_marginTop="100dp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{String.valueOf(goods.count)}"
            android:gravity="center"/>

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
重点来了,BindingAdapter
我们定义了set跟get,有了set就可以实现单向绑定
那是不是set跟get就可以实现双向绑定呢,还不够
我们还需要去做通知,用InverseBindingListener给自定义View设置InverseBindingListener
package com.pcf.customize.binding

import androidx.databinding.BindingAdapter
import androidx.databinding.InverseBindingAdapter
import androidx.databinding.InverseBindingListener

/**
 * binding 适配器
 * */
object BindingAdapters {

    @BindingAdapter(value = ["numberOfSets"], requireAll = false)
    @JvmStatic
    fun setNumberOfSets(view: GoodsCounterView, value: Long) {
        view.setText(value.toString())
    }

    @InverseBindingAdapter(attribute = "numberOfSets", event = "numberOfSetsAttrChanged")
    @JvmStatic
    fun getNumberOfSets(view: GoodsCounterView): Long {
        val text = view.getText()
        return if (text.isEmpty()) 0 else text.toLong()
    }

    @BindingAdapter(value = ["numberOfSetsAttrChanged"], requireAll = false)
    @JvmStatic
    fun setListener(view: GoodsCounterView, listener: InverseBindingListener?) {
        view.listener = listener
    }
}
现在回过头来看我们的GoodsCounterView类
定义了InverseBindingListener属性,并且在改变值的地方去做通知,这样就大功告成了
  var listener :InverseBindingListener ?= null

  private fun parseNum() {
        val text = editText.text.toString()
        if (text.isNotEmpty()) {
            var num = text.toLong()
            addButton.isEnabled = true
            reduceButton.isEnabled = num > 1
        } else {
            reduceButton.isEnabled = false
            addButton.isEnabled = false
        }
        listener?.onChange()
    }

文未放上Demo https://github.com/pengchengfuGit/customizebinding

你可能感兴趣的:(android,移动开发,安卓)