DataBinding 与 ViewBinding:从表达式到实战

说实话,刚接触 DataBinding 那会儿,我内心是抗拒的。总觉得 XML 里写表达式怪怪的,像回到了 JSP 时代。但真正用起来之后,嗯,真香。今天咱们就来聊聊这两个 Binding 家族的核心成员,以及它们在实际项目中的正确打开方式。

DataBinding 的表达式语言

DataBinding 最吸引人的地方,就是能在 XML 里直接写表达式。你想想看,以前我们要在 Activity 里 findViewById,然后 setText,现在直接在布局里搞定。

<layout>
    <data>
        <variable
            name="user"
            type="com.example.User" />
    </data>
    
    <TextView
        android:text="@{user.name}"
        android:visibility="@{user.isVIP ? View.VISIBLE : View.GONE}"
        ... />
</layout>

这里有几个关键点要注意:

  • 变量声明:用 <variable> 标签定义,类型要写全限定名
  • 表达式语法:用 @{} 包裹,支持三目运算符、字符串拼接、数学运算
  • 空安全处理@{user.name ?? "默认值"},这个我在项目里经常用
小技巧:表达式里可以直接引用 Android 的类,比如 View.VISIBLE、TextUtils.isEmpty 等,不需要额外 import。

双向绑定:数据与视图的实时同步

单向绑定是数据变了通知视图更新,双向绑定则是视图变了也通知数据更新。说白了,就是 EditText 输入内容时,背后的 ViewModel 数据也跟着变。

<EditText
    android:text="@={viewModel.userName}"
    ... />

注意看,这里用的是 @={},多了一个等号。这就是双向绑定的标志。

我曾经踩过的坑:双向绑定必须配合 Observable 或 LiveData 使用。如果你直接用普通 String,你会发现数据根本不会同步。为什么?因为 DataBinding 需要监听数据变化,普通字段没有通知机制。

我个人习惯在 ViewModel 里这样写:

class LoginViewModel : ViewModel() {
    val userName = MutableLiveData<String>()
    val password = ObservableField<String>()
}

两种方式都可以,LiveData 更推荐,因为它和 Lifecycle 绑定,不会泄漏。

BindingAdapter:自定义属性的魔法

有时候系统自带的属性不够用,比如我想给 ImageView 加一个加载网络图片的属性。这时候 BindingAdapter 就派上用场了。

@BindingAdapter("imageUrl")
fun loadImage(view: ImageView, url: String?) {
    if (url != null) {
        Glide.with(view.context).load(url).into(view)
    }
}

然后在 XML 里直接用:

<ImageView
    app:imageUrl="@{user.avatar}"
    ... />

这里有个细节:BindingAdapter 方法必须是静态的,第一个参数是 View 类型,第二个参数是属性值类型。我见过有人把参数顺序写反,结果运行时直接崩溃。

多参数 BindingAdapter:有时候一个属性不够,比如加载图片时需要同时传 url 和 placeholder。可以这样写:
@BindingAdapter(value = ["imageUrl", "placeholder"], requireAll = false)
fun loadImage(view: ImageView, url: String?, placeholder: Drawable?) {
    // 实现逻辑
}

ViewBinding:轻量级替代方案

如果你觉得 DataBinding 太重,只想省掉 findViewById,那 ViewBinding 就是为你准备的。它只做一件事:生成绑定类,让你直接访问视图。

// 在 build.gradle 里开启
android {
    buildFeatures {
        viewBinding = true
    }
}

// Activity 中使用
class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        
        binding.textView.text = "Hello ViewBinding"
    }
}

ViewBinding 和 DataBinding 的区别,我用一个表格来说明:

特性 DataBinding ViewBinding
表达式语言 支持 不支持
双向绑定 支持 不支持
BindingAdapter 支持 不支持
编译速度 较慢 更快
包体积影响 略大 几乎无影响

我的建议是:如果项目里只需要 findViewById 的替代,用 ViewBinding 就够了。如果需要数据驱动 UI,那就上 DataBinding。

在 RecyclerView 中使用 Binding

RecyclerView 的 Adapter 是 Binding 大显身手的地方。我以前写 Adapter 要写一堆 ViewHolder,现在简洁多了。

class UserAdapter(private val users: List<User>) : 
    RecyclerView.Adapter<UserAdapter.UserViewHolder>() {
    
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
        val binding = ItemUserBinding.inflate(
            LayoutInflater.from(parent.context), 
            parent, 
            false
        )
        return UserViewHolder(binding)
    }
    
    override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
        holder.bind(users[position])
    }
    
    override fun getItemCount() = users.size
    
    class UserViewHolder(private val binding: ItemUserBinding) : 
        RecyclerView.ViewHolder(binding.root) {
        
        fun bind(user: User) {
            binding.user = user
            binding.executePendingBindings() // 立即执行绑定,提升流畅度
        }
    }
}

注意 executePendingBindings() 这个方法。RecyclerView 滚动时,如果不调用它,绑定会延迟到下一帧,导致列表滑动卡顿。这是我实测过的优化点。

性能优化:在 onBindViewHolder 里调用 executePendingBindings(),可以让数据立即反映到视图上,避免布局闪烁。

知识体系总览

下面这张图概括了本章的核心内容,你可以看到 DataBinding 和 ViewBinding 的关系,以及各自的应用场景。

Binding 家族知识体系 Android Binding DataBinding ViewBinding 表达式语言 双向绑定 BindingAdapter 轻量级 无表达式 编译更快 RecyclerView 数据驱动 RecyclerView 视图绑定 选择依据:项目复杂度 > 编译速度 > 团队习惯

从图中可以看出,DataBinding 功能更全但更重,ViewBinding 轻巧但功能有限。实际项目中,我经常混合使用:核心页面用 DataBinding,简单列表用 ViewBinding。

好了,这一章的内容就到这里。Binding 家族的知识点不少,但核心就是一句话:让视图和数据的关系更清晰,让代码更简洁。下一章我们会深入 LiveData 和 ViewModel 的配合使用,敬请期待。

公众号:蓝海资料掘金营,微信deep3321