5、DataBinding:从配置到MVVM实战

DataBinding 这个组件,说实话刚出来的时候我还有点抵触。

心想:「不就是少写几行 findViewById 吗?至于搞这么大阵仗?」

直到我在一个大型电商项目里,面对几十个表单页面、成堆的 setText 和 setOnClickListener……嗯,真香了。

这一章,咱们就把 DataBinding 从配置到实战,掰开揉碎讲清楚。

5.1 环境配置:三步走

配置其实很简单,但有个坑我踩过——一定要在 app 模块的 build.gradle 里开启,别配到根项目去了。

android {
    ...
    buildFeatures {
        dataBinding = true
    }
}

同步完成后,你的布局文件需要「改造」一下。

把鼠标放到根布局上,Alt+Enter(Mac 是 Option+Enter),选择「Convert to data binding layout」。Android Studio 会自动帮你生成 <layout> 包裹层。

小技巧:我个人习惯手动写 <layout> 标签,因为自动转换有时会把命名空间搞乱。尤其是你用了自定义 View 的时候。

5.2 布局表达式:模板引擎的威力

DataBinding 的布局表达式,说白了就是在 XML 里写逻辑。

你想想看,以前我们要在 Activity 里写:

textView.text = user.name
textView.visibility = if (user.isVip) View.VISIBLE else View.GONE

现在直接在布局里搞定:

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="user"
            type="com.example.User" />
    </data>

    <LinearLayout ...>
        <TextView
            android:text="@{user.name}"
            android:visibility="@{user.isVip ? View.VISIBLE : View.GONE}" />
    </LinearLayout>
</layout>

这里要注意几个点:

  • 变量类型要写全路径,不然编译会报错。我刚开始老忘,后来干脆用 import 标签。
  • 三元表达式是常用的,但别写太复杂。超过两层嵌套?建议放到 ViewModel 里。
  • 空指针安全:DataBinding 会自动处理 user?.name 这种写法,不会崩。

5.3 双向绑定:少写一半代码

单向绑定是 @{},双向绑定是 @={}。多了一个等号,少了一半代码。

举个例子,EditText 和 ViewModel 里的字段同步:

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

这样用户输入时,viewModel.userName 会自动更新。不需要再写 addTextChangedListener 了。

注意:双向绑定要求字段类型是 ObservableField 或者 LiveData。直接用普通 String 是不行的,改了也不会通知 UI。

我曾经在一个搜索框里用了双向绑定,结果发现输入卡顿。排查了半天,原来是 ObservableField 的 set() 方法触发了太多通知。解决方案?用 Debounce 或者改用 LiveData 配合 Transformations

5.4 BindingAdapter:自定义属性的魔法

BindingAdapter 是我最喜欢的特性。它让你能给任何 View 添加自定义属性。

比如加载图片,以前要写:

Glide.with(context).load(url).into(imageView)

现在定义一个 BindingAdapter:

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

布局里直接写:

<ImageView
    app:imageUrl="@{viewModel.avatarUrl}" />

干净利落。

我常用的几个 BindingAdapter:

  • 加载图片(Glide / Coil)
  • 设置圆角(配合 ShapeableImageView)
  • 控制键盘弹出(EditText 自动获取焦点)
  • 列表空状态(RecyclerView 数据为空时显示空布局)

5.5 Observable:让数据活起来

DataBinding 的 Observable 家族有这几个成员:

类型 说明 适用场景
ObservableField<T> 单个字段 用户名、价格等
ObservableBoolean 布尔值 开关状态、可见性
ObservableInt 整型 数量、进度
ObservableArrayList 可观察列表 列表数据
ObservableArrayMap 可观察 Map 键值对数据

用法很简单:

class UserViewModel {
    val name = ObservableField<String>("张三")
    val age = ObservableInt(25)
    val isVip = ObservableBoolean(false)
}

在布局里绑定:

<TextView
    android:text="@{viewModel.name}" />

viewModel.name.set("李四") 时,UI 自动更新。

核心区别:Observable 是 DataBinding 自带的,不依赖 Lifecycle。而 LiveData 需要 LifecycleOwner。如果你只是做简单的 UI 绑定,Observable 更轻量。但如果你需要生命周期感知,建议用 LiveData。

5.6 实战:MVVM 基础架构

好了,理论说完了。咱们搭一个真正的 MVVM 架子。

先看整体结构:

MVVM + DataBinding 基础架构 View 层 Activity / Fragment 布局文件(.xml) ViewModel 层 UserViewModel ObservableField / LiveData Model 层 User 实体类 Repository / 数据源 绑定 调用 回调 通知 核心流程: 1. View 通过 DataBinding 绑定 ViewModel 中的 ObservableField 2. ViewModel 调用 Model 层获取数据 3. Model 层数据变化时,ViewModel 更新 ObservableField 4. DataBinding 自动将变化反映到 View 上

接下来是具体实现。先写一个 User 实体:

data class User(
    val id: Long,
    val name: String,
    val avatar: String,
    val isVip: Boolean
)

然后是 ViewModel:

class UserViewModel : ViewModel() {
    val userName = ObservableField<String>("")
    val avatarUrl = ObservableField<String>("")
    val isVip = ObservableBoolean(false)
    val loading = ObservableBoolean(false)

    private val repository = UserRepository()

    fun loadUser(userId: Long) {
        loading.set(true)
        viewModelScope.launch {
            try {
                val user = repository.getUser(userId)
                userName.set(user.name)
                avatarUrl.set(user.avatar)
                isVip.set(user.isVip)
            } catch (e: Exception) {
                // 处理错误
            } finally {
                loading.set(false)
            }
        }
    }
}

布局文件:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="viewModel"
            type="com.example.UserViewModel" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="16dp">

        <ImageView
            android:layout_width="80dp"
            android:layout_height="80dp"
            app:imageUrl="@{viewModel.avatarUrl}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{viewModel.userName}"
            android:textSize="18sp" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{viewModel.isVip ? @string/vip : @string/normal}"
            android:textColor="@{viewModel.isVip ? @color/gold : @color/gray}" />

        <ProgressBar
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:visibility="@{viewModel.loading ? View.VISIBLE : View.GONE}" />

    </LinearLayout>
</layout>

最后在 Activity 里绑定:

class UserActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = ActivityUserBinding.inflate(layoutInflater)
        setContentView(binding.root)

        val viewModel = ViewModelProvider(this)[UserViewModel::class.java]
        binding.viewModel = viewModel
        binding.lifecycleOwner = this

        viewModel.loadUser(123L)
    }
}
避坑指南:我曾经忘了设置 binding.lifecycleOwner,结果 LiveData 死活不更新。找了半天才发现是这个问题。如果你用 LiveData 做绑定,这行代码必须写。

这个架构的好处很明显:

  • View 层只负责布局和绑定,不写业务逻辑
  • ViewModel 层管理数据和状态,不持有 View 引用
  • Model 层处理数据来源,不关心 UI
  • DataBinding 作为胶水,自动同步数据和 UI

说白了,这就是一个「各司其职」的架构。每个组件只做自己该做的事,出了问题也容易定位。

嗯,这一章的内容就到这。DataBinding 的威力远不止这些,但掌握了这些基础,你已经能写出干净、可维护的 MVVM 代码了。


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