Android DataBinding 笔记四

  • Android DataBinding 笔记一
  • Android DataBinding 笔记二
  • Android DataBinding 笔记三
  • Android DataBinding 笔记四
  • 源码

这篇笔记主要记录绑定适配器和双向绑定的基本用法,这篇文章会包含完整的代码和运行效果。

绑定适配器

绑定适配器的作用就是给布局文件(xml)和特定方法之间起到相互绑定的作用的注解。 如下示例(其中invertColorBackground这个app的前缀是可以省略的,而且不省略AndroidStudio还会报警告:Application namespace for attribute app:colorValue will be ignored.):

@BindingAdapter("invertColorBackground")
fun setupInvertColorBackground(invertBackgroundColorView: View, colorValue: Int) {
...
}
@BindingAdapter("android:text")
fun setText(view: TextView, text: CharSequence) {
...
}

对应的xml应用方式:

<TextView
    ...
    android:text="..."
    app:invertColorBackground="@{color}" />

转换器

转换器用于将xml中其他的数据类型转换成绑定适配器需要的数据类型。如下定义:

@BindingConversion
fun convertColorToDrawable(color: ObservableInt) = ColorDrawable(color.get())

这个就是将xml中为ObservableInt的数据类型转换为ColorDrawable的数据类型,示例如下:

<variable name="color" type="androidx.databinding.ObservableInt" />
...
<View
    ...
    android:background="@{color}" />

这样就可以将ObservableInt直接设置为背景色了。

双向绑定

双向绑定的作用是在数据绑定的基础上,增加视图的变化时更新数据的功能。 如下示例:

<TextView
    ...
    app:colorValue="@={color}" />

是的没错,就是多了一个‘=’号。这样就是双向绑定的使用方法。那么如何定义呢?一共有三个步骤:

  1. 定义绑定适配器(BindingAdapter),数据改变时通知视图更新
@BindingAdapter(value = ["colorValue"])
fun setupColorValue(tvColorValue: TextView, colorValue: Int) {
    tvColorValue.setTextColor(colorValue)
    tvColorValue.text = String.format("0x%08x", colorValue).toUpperCase(Locale.getDefault())
}
  1. 定义反向绑定适配器(InverseBindingAdapter),定义视图更新时如何更新数据
@InverseBindingAdapter(attribute = "colorValue")
fun onColorValueChanged(tvColorValue: TextView): Int {
    return tvColorValue.currentTextColor
}
  1. 定义何时需要通知DataBinding去更新数据(注意,这里用的也是BindingAdapter注解,而且一定需要 AttrChanged 作为适配器中 colorValue 的后缀,不然会报错!)
@BindingAdapter(value = ["colorValueAttrChanged"])
fun setOnColorValueChanged(tvColorValue: TextView, attrChanged: InverseBindingListener) {
    tvColorValue.addTextChangedListener(object : TextWatcher {
        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
            Log.d(TAG, "beforeTextChanged: s = $s")
        }
        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
            Log.d(TAG, "onTextChanged: s = $s")
        }
        override fun afterTextChanged(s: Editable?) {
            Log.d(TAG, "afterTextChanged: s = $s")
            attrChanged.onChange()
        }
    })
}

这个示例实现了如下双向更新的功能:

  1. 当颜色值(colorValue)更新时,更新字体颜色和文字显示
  2. 当前字体颜色更新时,更新颜色值(colorValue)

完整代码示例

下面的代码会实现一个包含四个控件的视图:

  1. 功能开关按钮:用于开启和关闭自动变色,以及控制是否可以变色
  2. 手动变色按钮:用于直接让变色控件变色
  3. 变色控件:用于显示当前颜色
  4. 颜色提示控件:用于显示当前颜色值是多少

运行效果如下图:
Android DataBinding 笔记四_第1张图片
用到了以下知识点:

  1. 绑定适配器
  2. 双向绑定适配器
  3. DataBinding中如何应用merge布局

绑定适配器的定义

  1. 实现了TextView的文字于颜色值的双向绑定
  2. 根据颜色值显示其反色的背景
@BindingAdapter(value = ["colorValue"])
fun setupColorValue(tvColorValue: TextView, colorValue: Int) {
    tvColorValue.setTextColor(colorValue)
    tvColorValue.text = String.format("0x%08x", colorValue).toUpperCase(Locale.getDefault())
}
@InverseBindingAdapter(attribute = "colorValue")
fun onColorValueChanged(tvColorValue: TextView): Int {
    return tvColorValue.currentTextColor
}
@BindingAdapter(value = ["colorValueAttrChanged"])
fun setOnColorValueChanged(tvColorValue: TextView, attrChanged: InverseBindingListener) {
    tvColorValue.addTextChangedListener(object : TextWatcher {
        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
            Log.d(TAG, "beforeTextChanged: s = $s")
        }
        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
            Log.d(TAG, "onTextChanged: s = $s")
        }
        override fun afterTextChanged(s: Editable?) {
            Log.d(TAG, "afterTextChanged: s = $s")
            attrChanged.onChange()
        }
    })
}

@BindingAdapter(value = ["invertColorBackground"])
fun setupInvertColorBackground(invertBackgroundColorView: View, colorValue: Int) {
    val invertColor = (0xFF0000 - (colorValue and 0xFF0000))
        .or(0xFF00 - (colorValue and 0xFF00))
        .or(0xFF - (colorValue and 0xFF))
    Log.d(TAG, "setupInvertColorBackground: invertColor = $invertColor")
    invertBackgroundColorView.setBackgroundColor(Color.BLACK or invertColor)
}

@BindingConversion
fun convertColorToDrawable(color: ObservableInt) = ColorDrawable(color.get())

布局的实现

  1. activity_binding_adapter.xml 显示控制按钮
  2. merge_layout.xml 显示颜色控件

activity_binding_adapter.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="btnColorSwitch" type="androidx.databinding.ObservableBoolean" />
        <variable name="color" type="androidx.databinding.ObservableInt" />
        <variable name="onCheckedChangeListener" type="android.widget.CompoundButton.OnCheckedChangeListener" />
        <variable name="onClickListener" type="android.view.View.OnClickListener" />
    data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".BindingAdapterActivity">
        <ToggleButton
            android:id="@+id/btnSwitch"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:checked="@={btnColorSwitch}"
            android:onCheckedChanged="@{onCheckedChangeListener}"
            app:onCheckedChangeListener="@{onCheckedChangeListener}"/>
        <Button
            android:id="@+id/btnChangeColor"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/change_color"
            app:onClickListener="@{onClickListener}"/>
        <include
            layout="@layout/merge_layout"
            android:id="@+id/includeMergeLayout"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            bind:color="@{color}" />
    LinearLayout>
layout>

merge_layout.xml

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
        <variable name="color" type="androidx.databinding.ObservableInt" />
    data>
    <merge>
        <View
            android:id="@+id/vColor"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:background="@{color}" />
        <TextView
            android:id="@+id/tvColorValue"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:textAlignment="center"
            app:invertColorBackground="@{color}"
            app:colorValue="@={color}" />
    merge>
layout>

Activity的实现

  1. 每隔1.5秒自动改变color的颜色值
  2. 实现开关控制
  3. 实现单击变色
class BindingAdapterActivity : AppCompatActivity(), CompoundButton.OnCheckedChangeListener, View.OnClickListener{
    lateinit var activityBindingAdapterBinding: ActivityBindingAdapterBinding
    val color = ObservableInt(Color.WHITE)
    val btnColorSwitch = ObservableBoolean(false)
    val random = Random()
    var lastRandom = 0
    val DELAY_TO_CHANGE_COLOR = 1500L
    val mHandler = Handler(Looper.getMainLooper())
    val colorChanger = object : Runnable {
        override fun run() {
            color.set(randomColor())
            mHandler.postDelayed(this, DELAY_TO_CHANGE_COLOR)
        }
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        activityBindingAdapterBinding = DataBindingUtil.setContentView(this, R.layout.activity_binding_adapter)
        activityBindingAdapterBinding.onCheckedChangeListener = this
        activityBindingAdapterBinding.onClickListener = this
        activityBindingAdapterBinding.color = color
        activityBindingAdapterBinding.btnColorSwitch = btnColorSwitch
    }
    override fun onResume() {
        super.onResume()
        if (btnColorSwitch.get()) startChangeColor()
    }
    override fun onPause() {
        if (btnColorSwitch.get()) stopChangeColor()
        super.onPause()
    }
    override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) {
        if (isChecked) {
            startChangeColor()
        } else {
            stopChangeColor()
        }
    }
    override fun onClick(v: View) {
        when (v.id) {
            R.id.btnChangeColor -> {
                if (btnColorSwitch.get()) {
                    mHandler.removeCallbacks(colorChanger)
                    setupColorValue(activityBindingAdapterBinding.includeMergeLayout.tvColorValue, randomColor())
                    mHandler.postDelayed(colorChanger, DELAY_TO_CHANGE_COLOR)
                } else {
                    Log.d(TAG, "onCreate: btnColorSwitch = false")
                }
            }
        }
    }
    private fun startChangeColor() {
        mHandler.removeCallbacks(colorChanger)
        mHandler.postDelayed(colorChanger, DELAY_TO_CHANGE_COLOR)
    }
    private fun stopChangeColor() {
        mHandler.removeCallbacks(colorChanger)
    }
    private fun randomColor(): Int {
        val nextInt = random.nextInt(7)
        lastRandom = if (lastRandom == nextInt) (nextInt + 1) % 7 else nextInt
        return when (lastRandom) {
            0 -> Color.WHITE
            1 -> Color.RED
            2 -> Color.BLUE
            3 -> Color.YELLOW
            4 -> Color.CYAN
            5 -> Color.MAGENTA
            else -> Color.BLACK
        }
    }
}

可以看到配置视图数据和状态等操作全部都交给了DataBinding,而这里只需要负责给数据赋值,实现变更逻辑。这样我们可以将流程设计和具体实现给分开,让代码逻辑更加清晰,代码管理更加方便。

你可能感兴趣的:(Jetpack,jetpack)