数据绑定与 ViewBinding
本教程共 100 篇 · 第 96 篇 · 更新于 2026-07-28 · 约 7 分钟阅读
96. 数据绑定与 ViewBinding
本节目标:理解 ViewBinding 和 DataBinding 的区别和用法,学会用 ViewBinding 替代 findViewById、用 DataBinding 在 XML 写表达式、双向绑定,知道 Compose 时代还需不需要。
View 体系里,找 View 用 findViewById,类型不安全、容易 NPE。ViewBinding 和 DataBinding 是两个替代方案。Compose 项目基本不用,但老代码维护要会。
两者区别
| 特性 | ViewBinding | DataBinding |
|---|---|---|
| 功能 | 类型安全找 View | 找 View + XML 写表达式 + 双向绑定 |
| 编译速度 | 快 | 慢(要生成额外代码) |
| 包体积 | 小 | 大 |
| 学习成本 | 低 | 中 |
| Compose 时代 | 还能用 | 完全没必要 |
简单场景用 ViewBinding,复杂绑定用 DataBinding。Compose 项目直接不用。
ViewBinding
开启
build.gradle.kts:
android {
buildFeatures {
viewBinding = true
}
}
用法
每个 XML 布局会生成一个绑定类,类名是 XML 名转 PascalCase + Binding。比如 activity_main.xml -> ActivityMainBinding。
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// 直接用 binding.viewId
binding.titleText.text = "Hello"
binding.submitButton.setOnClickListener {
// ...
}
}
}
XML:
<LinearLayout ...>
<TextView android:id="@+id/title_text" ... />
<Button android:id="@+id/submit_button" ... />
</LinearLayout>
title_text 变成 binding.titleText,类型是 TextView。不用强转,不用判空(除非 visibility 是 gone 且没 id)。
Fragment 里用
class MyFragment : Fragment() {
private var _binding: MyFragmentBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = MyFragmentBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null // 防止泄漏
}
}
WarningFragment 的视图生命周期比 Fragment 短,
onDestroyView要把 binding 置 null,否则持有已销毁的 View 泄漏。
RecyclerView Adapter
class MyAdapter : RecyclerView.Adapter<MyAdapter.VH>() {
class VH(val binding: ItemBinding) : RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
val binding = ItemBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
return VH(binding)
}
override fun onBindViewHolder(holder: VH, position: Int) {
holder.binding.title.text = items[position]
}
}
include 布局
<include
android:id="@+id/included"
layout="@layout/included_layout" />
binding.included.someTextView.text = "..."
include 必须有 id 才能 access。
DataBinding
开启
android {
buildFeatures {
dataBinding = true
}
}
XML 改造
DataBinding 的 XML 要包一层 <layout>:
<layout xmlns:android="...">
<data>
<variable
name="user"
type="com.example.User" />
</data>
<LinearLayout ...>
<TextView
android:text="@{user.name}"
... />
<TextView
android:text="@{String.valueOf(user.age)}"
... />
</LinearLayout>
</layout>
绑定数据
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityMainBinding = DataBindingUtil.setContentView(
this, R.layout.activity_main
)
binding.user = User("张三", 28)
}
}
XML 里的 @{user.name} 自动绑定。
表达式
XML 里能写简单表达式:
<TextView
android:text="@{user.firstName + ' ' + user.lastName}"
android:visibility="@{user.age >= 18 ? View.VISIBLE : View.GONE}" />
支持的运算:
- 算术:
+ - * / % - 字符串拼接:
+ - 逻辑:
&& || ! - 比较:
== != > < >= <= - 三元:
?: - null 安全:
??、?.
Observable 数据
数据变化时自动更新 UI:
class User : BaseObservable() {
@get:Bindable
var name: String = ""
set(value) {
field = value
notifyPropertyChanged(BR.name)
}
}
或者用 ObservableField:
class User {
val name = ObservableField<String>("")
}
binding.user = User().apply { name.set("张三") }
双向绑定
@={} 表示双向:UI 改了同步回数据。
<EditText
android:text="@={user.name}" />
用户输入会自动写到 user.name。
BindingAdapter
自定义 XML 属性:
@BindingAdapter("imageUrl")
fun ImageView.loadImage(url: String?) {
if (url != null) {
Glide.with(this).load(url).into(this)
}
}
XML 用:
<ImageView
app:imageUrl="@{user.avatarUrl}" />
ViewBinding vs DataBinding
| 场景 | 选什么 |
|---|---|
| 只想替代 findViewById | ViewBinding |
| 想在 XML 写表达式 | DataBinding |
| 双向绑定 | DataBinding |
| 自定义属性绑定 | DataBinding |
| 性能要求高 | ViewBinding |
| Compose 项目 | 都不用 |
TipViewBinding 简单可靠,90% 场景够用。DataBinding 功能强但编译慢,复杂场景才用。
Compose 时代
Compose 完全不需要 ViewBinding 和 DataBinding:
- 找 View 不存在了,Composable 直接调。
- 状态绑定就是
mutableStateOf+ Composable 参数。 - 双向绑定就是状态提升。
@Composable
fun NameInput() {
var name by remember { mutableStateOf("") }
TextField(
value = name,
onValueChange = { name = it }
)
}
这是「双向绑定」的等价物,但更简单清晰。
迁移建议
老项目从 findViewById 迁移:
- 第一步:开 ViewBinding,所有
findViewById换成binding.xxx。 - 第二步:复杂页面用 DataBinding,把 UI 逻辑移到 XML。
- 第三步:新功能用 Compose,逐步替换。
不建议 DataBinding 老代码强行迁到 ViewBinding,差异大、收益小。直接迁 Compose 更划算。
常见坑
- ViewBinding 不生成:检查
buildFeatures.viewBinding = true,Sync 一下。 - Fragment 里 binding 没置空:泄漏,
onDestroyView必须 null。 - include 没 id:访问不到。
- DataBinding 表达式语法错:编译报错,但错误信息不直观,仔细看。
- DataBinding 用错
@{}和@={}:单向双向混。 - ObservableField vs Flow:DataBinding 老 API,新代码用 StateFlow + observeAsState。
BR类不生成:DataBinding 编译产物, rebuild 一下。
小结
ViewBinding 类型安全替代 findViewById,简单可靠。DataBinding 在 XML 写表达式、支持双向绑定和 BindingAdapter,复杂场景用。开启方式是 buildFeatures 里加 viewBinding / dataBinding。Compose 项目完全不需要这两套,状态提升就是天然的绑定。老项目维护要会,新项目用 Compose。
下一章讲依赖注入。