注册

android ViewBinding

1. 它是什么 & 有啥用



  • 编译期生成与每个布局一一对应的 XXXBinding 类,帮你类型安全地拿到 View 引用;没有反射、没有运行时开销。
  • 仅做“找 View”,不包含表达式/双向绑定/观察者(那是 DataBinding 的职责)。

2. 开启方式(Gradle)


android {
buildFeatures { viewBinding = true }
}


  • 应用/库模块都可开;想排除某些布局,给布局根元素加:

<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


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(避免内存泄漏的标准写法)


class HomeFragment : Fragment() {
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!

override fun onCreateView(
inflater: LayoutInflater, container: ViewGr0up?, 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。



可选:更安全的委托


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


class UserVH(val binding: ItemUserBinding) : RecyclerView.ViewHolder(binding.root)

override fun onCreateViewHolder(parent: ViewGr0up, 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

<include
android:id="@+id/header"
layout="@layout/include_header"/>


  • 使用:

binding.header.title.text = "Title"


  • merge 根布局:不产生多余容器,使用:

val b = IncludeToolbarBinding.inflate(inflater, parent, /*attachToParent=*/true)
// 注意:merge 必须 attachToParent = true

7. Dialog / BottomSheet / AlertDialog


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):

@Composable
fun LegacyCard() {
AndroidViewBinding(factory = LegacyCardBinding::inflate) {
title.text = "Hello from ViewBinding"
}
}

9. 常见坑 & 排查



  1. Fragment 泄漏:忘记在 onDestroyView() 置空 _binding。—— 现象:导航返回/旋转后崩溃或持有旧 View。
  2. merge 布局用了 attachToParent=false:导致 IllegalStateException 或看不见 UI。
  3. 在 onCreate() 就用 Fragment 的 binding:此时 View 还没创建,应在 onViewCreated() 之后使用。
  4. 重复 inflate:同一布局多次 inflate 却多次 setContentView/addView,导致层级重复/点击穿透。
  5. 多模块命名冲突:不同模块同名布局会各自产生 Binding,不会冲突;若共享资源注意命名前缀。
  6. 列表里频繁创建 binding:放在 onCreateViewHolder,不要在 onBindViewHolder 重复 inflate。

10. 实战小抄(可直接套用)


(1)列表条目 ViewHolder 模板)


class MsgVH(val b: ItemMsgBinding) : RecyclerView.ViewHolder(b.root)
override fun onCreateViewHolder(p: ViewGr0up, 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


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 组合标题栏


<!-- layout: activity_main.xml -->
<LinearLayout ...>
<include
android:id="@+id/toolbar"
layout="@layout/include_toolbar"/>

<!-- page content -->
</LinearLayout>

binding.toolbar.title.text = "主页"
binding.toolbar.back.setOnClickListener { onBackPressedDispatcher.onBackPressed() }

作者:南北是北北
来源:juejin.cn/post/7561077821995630644

0 个评论

要回复文章请先登录注册