Data Binding Conversion 详解

103 阅读3分钟

Data Binding Conversion 详解

这个 BindingConversion 的用法虽然简单,但背后有一些重要机制。我来详细解释一下这个用法背后的原理,以及各个部分的可定制性。

核心问题分析

原始 XML:

<ImageView
    android:background="@{true ? @color/colorRed : @color/colorBlue}"

这里存在​​类型不匹配​​:

  • android:background 需要 Drawable 类型
  • @color/colorRed@color/colorBlue 是​​资源ID​​(即 Int 类型)

为了解决这个类型不匹配,我们添加了 BindingConversion

@JvmStatic
@BindingConversion
fun colorToDrawable(color: Int): ColorDrawable? {
    return ColorDrawable(color)
}

BindingConversion 工作机制

graph TD
    A[XML中的<br/>@{@color/colorRed}] --> B[DataBinding计算表达式] --> C{结果类型检查}
    C -->|类型 Int| D[在转换器中查找匹配方法]
    D --> E[找到 colorToDrawable(Int)]
    E --> F[调用转换方法]
    F --> G[返回 ColorDrawable]
    G --> H[设置为 View 的背景]

为什么需要这个转换?

没有转换时:

  1. DataBinding 得到表达式结果是 Int 类型
  2. View.setBackground() 需要 Drawable 类型
  3. 没有找到合适的转换方法 → 崩溃

命名规则和可定制性

部分是否可自定义命名规则说明
​方法名称​colorToDrawable✅ 完全自由无限制,描述其功能即可建议使用描述转换功能的名称
​参数名称​color✅ 完全自由无限制建议描述数据来源
​参数类型​color: Int❌ 必须匹配必须匹配表达式结果类型这里是 Int (资源ID)
​返回类型​ColorDrawable⚠️ 部分自由必须匹配目标属性类型或其父类可以是 Drawable 或其子类
​注解​@BindingConversion❌ 固定必须是 @BindingConversion标记这是一个类型转换方法
​注解​@JvmStatic✅ 但建议添加非必需但强烈推荐使方法在 JVM 中成为静态方法

工作原理详解

1. 类型匹配触发

当 Data Binding 发现:

  1. XML 表达式的返回类型(Int)
  2. 与目标属性类型(Drawable)
  3. ​不匹配​

它会自动查找所有带有 @BindingConversion 的方法:

// 查找规则
@BindingConversion
fun [任意名称](sourceType: 来源类型): 目标类型? { ... }

2. 转换方法选择

DataBinding 优先选择:

  1. 参数类型完全匹配的转换方法
  2. 方法可见性:必须是 public
  3. 静态方法优先(因此推荐使用 @JvmStatic

更好的实践

方案1:创建通用资源转换器(推荐)

object ResourceBindingConverters {

    @JvmStatic
    @BindingConversion
    fun intToColorDrawable(colorRes: Int): ColorDrawable? {
        return ColorDrawable(Context.getColor(colorRes))
    }

    @JvmStatic
    @BindingConversion
    fun intToBitmapDrawable(drawableRes: Int): BitmapDrawable? {
        return BitmapDrawableFactory.fromResource(drawableRes)
    }
}

方案2:使用绑定适配器(更清晰)

object ViewBindingAdapters {

    @JvmStatic
    @BindingAdapter("android:background")
    fun setBackgroundResource(view: View, resId: Int) {
        if (resId == 0) {
            view.background = null
        } else {
            view.setBackgroundResource(resId)
        }
    }
}

这样 XML 保持不变:

<ImageView
    android:background="@{true ? @color/colorRed : @color/colorBlue}"

方案3:使用条件绑定适配器

@BindingAdapter("conditionalBackground")
@JvmStatic
fun setConditionalBackground(view: View, condition: Boolean) {
    val color = if (condition) 
        view.context.getColor(R.color.colorRed)
    else 
        view.context.getColor(R.color.colorBlue)
    
    view.setBackgroundColor(color)
}

XML 使用:

<ImageView
    app:conditionalBackground="@{true}"/>

BindingConversion 的优势与局限性

优点:

  1. ​全局应用​​:应用到所有类型匹配的场景
  2. ​简洁​​:减少适配器代码
  3. ​自动调用​​:无需显式在XML中使用

缺点:

  1. ​不够明确​​:转换是隐式的
  2. ​难以调试​​:转换错误不易发现
  3. ​作用范围过大​​:可能影响其他同类型转换

最佳实践总结

  1. ​小范围类型转换​​:使用 BindingConversion

    // 颜色资源ID转颜色值
    @BindingConversion
    fun colorResToInt(@ColorRes colorRes: Int): Int {
        return ContextCompat.getColor(context, colorRes)
    }
    
  2. ​大范围定制逻辑​​:使用 BindingAdapter

    @BindingAdapter("tintedBackground")
    fun setTintedBackground(view: View, @ColorInt color: Int) {
        view.background?.mutate()?.colorFilter = 
            PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN)
    }
    
  3. ​避免过度依赖转换​​:复杂逻辑应放在ViewModel中计算

完整可用的颜色解决方案

colors.xml

<resources>
    <color name="colorRed">#F44336</color>
    <color name="colorBlue">#2196F3</color>
    <color name="colorTransparent">#00000000</color>
</resources>

转换器实现

object ColorBindingConverters {

    @JvmStatic
    @BindingConversion
    fun intToColorDrawable(colorRes: Int): Drawable {
        // 0是默认的空值处理
        if (colorRes == 0) return ColorDrawable(Color.TRANSPARENT)
        return ColorDrawable(ContextCompat.getColor(context, colorRes))
    }
    
    @JvmStatic
    @BindingAdapter("android:background")
    fun setBackgroundColorRes(view: View, @ColorRes colorRes: Int) {
        if (colorRes == 0) {
            view.setBackgroundResource(0)
        } else {
            view.setBackgroundResource(colorRes)
        }
    }
}

XML 使用

<ImageView
    android:background="@{showRed ? R.color.colorRed : R.color.colorBlue}"/>

这样既解决了类型转换问题,又保持了代码的可读性和安全性。