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 自动更新。
5.6 实战:MVVM 基础架构
好了,理论说完了。咱们搭一个真正的 MVVM 架子。
先看整体结构:
接下来是具体实现。先写一个 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 代码了。