1. 它是什么 & 有啥用
-
编译期生成 与每个布局一一对应的 XXXBinding 类,帮你类型安全地拿到 View 引用;没有反射、没有运行时开销。
-
仅做"找 View",不包含表达式/双向绑定/观察者(那是 DataBinding 的职责)。
2. 开启方式(Gradle)
ini
android {
buildFeatures { viewBinding = true }
}
- 应用/库模块都可开;想排除某些布局,给布局根元素加:
ini
<LinearLayout
xmlns:tools="http://schemas.android.com/tools"
tools:viewBindingIgnore="true" ... />
3. 生成类与命名规则
-
activity_main.xml → ActivityMainBinding
-
item_user_info.xml → ItemUserInfoBinding
-
只为有 id 的 View生成字段;布局根通过 binding.root 访问。
4. 三大常用场景
4.1 Activity
kotlin
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.title.text = "Hello"
binding.button.setOnClickListener { /* ... */ }
}
}
4.2 Fragment(避免内存泄漏的标准写法)
kotlin
class HomeFragment : Fragment() {
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
_binding = FragmentHomeBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.list.adapter = adapter
}
override fun onDestroyView() {
_binding = null // 关键:与 View 的生命周期对齐
}
}
只在 onCreateView ~ onDestroyView 之间使用 binding;不要持有到 Fragment 的字段里跨越 onDestroyView。
可选:更安全的委托
kotlin
class ViewBindingDelegate<T: ViewBinding>(
val fragment: Fragment,
val binder: (View) -> T
) : ReadOnlyProperty<Fragment, T> {
private var binding: T? = null
override fun getValue(thisRef: Fragment, property: KProperty<*>): T =
binding ?: binder(thisRef.requireView()).also {
binding = it
thisRef.viewLifecycleOwner.lifecycle.addObserver(object: DefaultLifecycleObserver {
override fun onDestroy(owner: LifecycleOwner) { binding = null }
})
}
}
fun <T: ViewBinding> Fragment.viewBinding(binder: (View)->T) =
ViewBindingDelegate(this, binder)
// 用法:private val binding by viewBinding(FragmentHomeBinding::bind)
4.3 RecyclerView.ViewHolder
kotlin
class UserVH(val binding: ItemUserBinding) : RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserVH {
val inflater = LayoutInflater.from(parent.context)
return UserVH(ItemUserBinding.inflate(inflater, parent, false))
}
override fun onBindViewHolder(holder: UserVH, position: Int) {
val item = getItem(position)
holder.binding.name.text = item.name
}
5. inflate / bind 的三种入口
-
XXXBinding.inflate(layoutInflater):常用于 Activity。
-
XXXBinding.inflate(inflater, parent, attachToParent):用于列表/Fragment。
- 根为 的布局:必须提供非空 parent,且 attachToParent=true。
-
XXXBinding.bind(view):当你已有一个 View(比如 Dialog#setContentView(view) 后)再创建 binding。
6. include / merge 的细节
- include :给 一个 android:id,生成的字段类型直接是被包含布局的 Binding:
ini
<include
android:id="@+id/header"
layout="@layout/include_header"/>
- 使用:
ini
binding.header.title.text = "Title"
- merge 根布局:不产生多余容器,使用:
ini
val b = IncludeToolbarBinding.inflate(inflater, parent, /*attachToParent=*/true)
// 注意:merge 必须 attachToParent = true
7. Dialog / BottomSheet / AlertDialog
kotlin
class EditDialogFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val b = DialogEditBinding.inflate(layoutInflater)
return AlertDialog.Builder(requireContext())
.setView(b.root)
.setPositiveButton("OK") { _, _ -> /* read b.editText */ }
.create()
}
}
8. 与 DataBinding / Compose 的区别与选型
- ViewBinding :只做找 View,最快、最轻 ,无 包裹、无表达式。推荐大多数传统 View 项目使用。
- DataBinding:支持 @{} 表达式、@BindingAdapter、双向绑定 @={};复杂但强大,编译慢、心智成本高。
- Compose:声明式 UI。新项目优先;老项目可"渐进式"在局部用 ComposeView。
- Compose × ViewBinding 互操作:在 Compose 内直接用 AndroidViewBinding(依赖 ui-viewbinding):
kotlin
@Composable
fun LegacyCard() {
AndroidViewBinding(factory = LegacyCardBinding::inflate) {
title.text = "Hello from ViewBinding"
}
}
9. 常见坑 & 排查
-
Fragment 泄漏:忘记在 onDestroyView() 置空 _binding。------ 现象:导航返回/旋转后崩溃或持有旧 View。
-
merge 布局用了 attachToParent=false:导致 IllegalStateException 或看不见 UI。
-
在 onCreate() 就用 Fragment 的 binding:此时 View 还没创建,应在 onViewCreated() 之后使用。
-
重复 inflate:同一布局多次 inflate 却多次 setContentView/addView,导致层级重复/点击穿透。
-
多模块命名冲突:不同模块同名布局会各自产生 Binding,不会冲突;若共享资源注意命名前缀。
-
列表里频繁创建 binding:放在 onCreateViewHolder,不要在 onBindViewHolder 重复 inflate。
10. 实战小抄(可直接套用)
(1)列表条目 ViewHolder 模板)
kotlin
class MsgVH(val b: ItemMsgBinding) : RecyclerView.ViewHolder(b.root)
override fun onCreateViewHolder(p: ViewGroup, vt: Int) =
MsgVH(ItemMsgBinding.inflate(LayoutInflater.from(p.context), p, false))
override fun onBindViewHolder(h: MsgVH, pos: Int) = with(h.b) {
title.text = getItem(pos).title
time.text = getItem(pos).time
}
(2)Fragment × ViewBinding × Lifecycle
kotlin
override fun onViewCreated(v: View, s: Bundle?) {
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.ui.collect { ui -> binding.progress.isVisible = ui.loading }
}
}
}
(3)include 组合标题栏
xml
<!-- layout: activity_main.xml -->
<LinearLayout ...>
<include
android:id="@+id/toolbar"
layout="@layout/include_toolbar"/>
<!-- page content -->
</LinearLayout>
ini
binding.toolbar.title.text = "主页"
binding.toolbar.back.setOnClickListener { onBackPressedDispatcher.onBackPressed() }