上一篇聊了 Compose 自定义布局,这篇回到 View 体系,聊一个迁移期的经典问题——ViewBinding vs DataBinding。很多项目同时用了两者,代码混乱。到底该留哪个?怎么清理?
两者的本质区别
Google 的态度很明确:ViewBinding 是 View 体系下推荐的方案,DataBinding 只在需要数据绑定的场景保留。新项目不要用 DataBinding。
ViewBinding 用法
// build.gradle
android {
viewBinding {
enable = true
}
}
// XML: activity_main.xml
// <TextView android:id="@+id/tvTitle" />
// <Button android:id="@+id/btnAction" />
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.tvTitle.text = "Hello"
binding.btnAction.setOnClickListener { /* ... */ }
}
}
ViewBinding 生成的类名规则:XML 文件名转 PascalCase + "Binding"。activity_main.xml → ActivityMainBinding。
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 // 必须!Fragment 的 View 生命周期比 Fragment 短
}
}
Fragment 中必须置空 binding,否则 View 销毁后还持有引用导致内存泄漏。这是 ViewBinding 最常见的坑。
RecyclerView Adapter 中的用法
class MyAdapter : RecyclerView.Adapter<MyAdapter.ViewHolder>() {
class ViewHolder(val binding: ItemViewBinding) : RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = ItemViewBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.binding.tvTitle.text = items[position].title
}
}
include 布局的引用
<!-- activity_main.xml -->
<include android:id="@+id/includedLayout" layout="@layout/included_content" />
// included_layout.xml 里的 View 通过 binding.includedLayout.xxx 访问
binding.includedLayout.tvTitle.text = "Hello"
include 必须有 android:id,否则 ViewBinding 不会生成引用。
DataBinding 还在用的场景
如果项目已经有大量 DataBinding XML 表达式(@{}),不建议全部拆掉。常见还需要的场景:
双向绑定:
@={viewModel.text}可以自动同步 EditText 输入到 ViewModel条件表达式:
@{user.online ? visible : gone}在 XML 里做显示控制BindingAdapter:自定义 XML 属性绑定逻辑
@BindingAdapter("imageUrl")
fun loadImage(view: ImageView, url: String?) {
Glide.with(view).load(url).into(view)
}
但说实话,这些场景用 Kotlin 代码实现也不复杂,而且更易调试。DataBinding 的 XML 表达式在编译期检查不严格,运行时才报错,调试体验不好。
混用时的注意事项
ViewBinding 和 DataBinding 可以同时使用。如果 XML 根节点有 <layout> 标签,会生成 DataBinding 类;否则生成 ViewBinding 类。
<!-- 这个 XML 生成 ViewBinding 类 -->
<LinearLayout>...</LinearLayout>
<!-- 这个 XML 生成 DataBinding 类 -->
<layout>
<data>
<variable name="user" type="com.example.User" />
</data>
<LinearLayout>...</LinearLayout>
</layout>
两种生成的类名规则不同:ViewBinding 是 XxxBinding,DataBinding 也是 XxxBinding,但 DataBinding 多了 bind() 方法和数据变量。
从 DataBinding 迁移到 ViewBinding
如果你决定清理 DataBinding:
删除
<layout>标签,把 XML 恢复成普通布局把
@{}表达式改成代码赋值,移到onBindViewHolder或onViewCreatedBindingAdapter 改成扩展函数,在代码中调用
双向绑定改成 LiveData/Flow 观察,在代码中
afterTextChanged时手动同步
这个过程比较繁琐但机械,适合分模块逐步进行。优先迁移简单页面,复杂的(有大量表达式)最后处理。
Compose 时代的终结
如果你的项目在向 Compose 迁移,ViewBinding 和 DataBinding 最终都会被替代。在 Compose 中不需要 View 引用:
@Composable
fun MyScreen(viewModel: MyViewModel = viewModel()) {
val state by viewModel.state.collectAsState()
Text(text = state.title)
Button(onClick = { viewModel.onAction() }) { Text("Click") }
}
没有 binding,没有 findViewById,没有 NPE 风险。所以如果是新项目或大重构,直接上 Compose 比 ViewBinding 更省心。
小结
ViewBinding 是 View 体系下最简洁的方案,新项目直接用。DataBinding 在存量项目中可以保留但不要新增。长期目标是 Compose,ViewBinding 只是过渡。如果团队短期不打算上 Compose,ViewBinding 足够好用。
下一篇聊聊 Android Wear OS 开发要点。