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 ?? "默认值"},这个我在项目里经常用
双向绑定:数据与视图的实时同步
单向绑定是数据变了通知视图更新,双向绑定则是视图变了也通知数据更新。说白了,就是 EditText 输入内容时,背后的 ViewModel 数据也跟着变。
<EditText
android:text="@={viewModel.userName}"
... />
注意看,这里用的是 @={},多了一个等号。这就是双向绑定的标志。
我个人习惯在 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(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 滚动时,如果不调用它,绑定会延迟到下一帧,导致列表滑动卡顿。这是我实测过的优化点。
知识体系总览
下面这张图概括了本章的核心内容,你可以看到 DataBinding 和 ViewBinding 的关系,以及各自的应用场景。
从图中可以看出,DataBinding 功能更全但更重,ViewBinding 轻巧但功能有限。实际项目中,我经常混合使用:核心页面用 DataBinding,简单列表用 ViewBinding。
好了,这一章的内容就到这里。Binding 家族的知识点不少,但核心就是一句话:让视图和数据的关系更清晰,让代码更简洁。下一章我们会深入 LiveData 和 ViewModel 的配合使用,敬请期待。