JetPack ViewBinding

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. 常见坑 & 排查

  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 模板)

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() }
相关推荐
南北是北北4 小时前
jetpack ViewModel
面试
渣哥5 小时前
Lazy能否有效解决循环依赖?答案比你想的复杂
javascript·后端·面试
前端架构师-老李6 小时前
面试问题—你接受加班吗?
面试·职场和发展
ANYOLY6 小时前
多线程&并发篇面试题
java·面试
南北是北北6 小时前
RecyclerView 的数据驱动更新
面试
uhakadotcom6 小时前
coze的AsyncTokenAuth和coze的TokenAuth有哪些使用的差异?
后端·面试·github
Chejdj6 小时前
StateFlow、SharedFlow 和LiveData区别
android·面试
道可到6 小时前
直接可以拿来的面经 | 从JDK 8到JDK 21:一次团队升级的实战经验与价值复盘
java·面试·架构
南北是北北7 小时前
RecyclerView 进阶绑定:多类型 / 局部刷新(payload)/ 稳定 ID
面试