竟然如此简单,DataBinding 和 ViewBinding
这是 dhl 的第 28 篇原创文章
首先祝小伙伴们新年快乐,2020 一个不平凡的一年,2021 是你我新的起点。
2021 新签名:代码不止,文章不停。
2021 第一篇文章是对 2020 年末最后一篇文章 Kotlin 插件的落幕,ViewBinding 的崛起 的一个补充。
在之前的文章 Kotlin 插件的落幕,ViewBinding 的崛起 中介绍了 Google 为什么不建议在项目中使用 Kotlin 合成方法(Synthetic 视图), Google 建议使用 ViewBinding 替换 Kotlin 合成方法,那么 ViewBinding 和 DataBinding 都有什么区别。
ViewBinding:
仅仅支持绑定 View 不需要在布局文件中添加 layout 标签 需要在模块级 build.gradle
文件中添加viewBinding = true
即可使用效率高于 DataBinding,因为避免了与数据绑定相关的开销和性能问题 相比于 kotlin-android-extensions
插件避免了空异常
DataBinding:
包含了 ViewBinding 所有的功能 需要在模块级 build.gradle
文件内添加dataBinding = true
并且需要在布局文件中添加 layout 标签才可以使用支持 data 和 view 双向绑定 效率低于 ViewBinding,因为注释处理器会影响数据绑定的构建时间。
ViewBinding 可以实现的, DataBinding 都可以实现,但是 DataBinding 的性能低于 ViewBinding,DataBinding 和 ViewBinding 会为每个 XML 文件生成绑定类。
R.layout.activity_main -> ActivityMainBinding
R.layout.fragment_main -> FragmentMainBinding
R.layout.dialog_app -> DialogAppBinding
在 Kotlin 插件的落幕,ViewBinding 的崛起 文章中同时也分析了 Kotlin 合成方法所带来的问题。即使 Kotlin 合成方法有很多问题,但是还有小伙伴愿意使用。
ViewBinding 和 DataBinding 为我们解决了这么多问题,但是为什么很多小伙伴们不愿意使用 ViewBinding 和 DataBinding,今天我们从使用的角度来分析。
ViewBinding 和 DataBinding
我大概汇总了 ViewBinding 和 DataBinding 在不同场景的所有用法,我们来看一下在项目中如何使用。
基本配置
从 Android Studio 3.6
版本开始,就内置在 Gradle 插件中了,不需要添加任何额外的库来使用它们,但是在 Android Studio 3.6
和 Android Studio 4.0
中使用方式不一样。
// Android Studio 3.6
android {
viewBinding {
enabled = true
}
dataBinding{
enabled = true
}
}
// Android Studio 4.0
android {
buildFeatures {
dataBinding = true
viewBinding = true
}
}
ViewBinding 的使用
因为涉及到的场景比较多,为了减少篇幅,我只列出来核心部分,如果之前从来没有用过,这里只需要知道 ViewBinding 的门槛比 Kotlin 合成方法要高即可。
不想为某个布局生成 binding 类,将下面属性添加到布局文件的根视图中
<LinearLayout tools:viewBindingIgnore="true" >
</LinearLayout>
在 Activity 中使用
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityMainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
}
在 Fragment 中使用
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
val binding = FragmentViewBindBinding.inflate(inflater,container,false)
return binding.root
}
在 Adapter 中的使用
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
RecycleItemProductBinding.inflate(LayoutInflater.from(parent.context), parent, false)
}
在 Dialog 中使用
override fun onCreate(savedInstanceState: Bundle?) {
binding = DialogAppBinding.inflate(layoutInflater)
setContentView(binding.root)
}
include 标签的使用
include
标签不带 merge
标签,需要给 include
标签添加 id, 直接使用 id 即可,用法如下所示。
<include
android:id="@+id/include"
layout="@layout/layout_include_item" />
val binding: ActivityMainBinding = ActivityMainBinding.inflate(layoutInflater)
binding.include.includeTvTitle.setText("使用 include 布局中的控件, 不包含 merge")
include
标签带 merge
标签,注意这里和 DataBinding 用法不一样,给 include
标签添加 id,在 DataBinding 中可以直接使用 id,ViewBinding 则不行,ViewBinding 的用法如下所示。
<include layout="@layout/layout_merge_item" />
val binding: ActivityMainBinding = ActivityMainBinding.inflate(layoutInflater)
val mergeItemBinding = LayoutMergeItemBinding.bind(binding.root)
mergeItemBinding.mergeTvTitle.setText("使用 include 布局中的控件, 包含 merge")
ViewStub 标签的使用
根据实践证明,截止到这篇文章发布时,在 Android Studio 4.2.0 bata 2
中,无法直接在 ViewBinding 布局中使用 ViewStub
标签,仅仅只能在 DataBinding 布局(带 layout
标签)中使用,详见 issue
https://stackoverflow.com/questions/58198298/android-3-6-viewstubproxy-unresolved-reference
因为没有找到比较权威的资料证明,这里建议小伙们直接在项目 Binding 中进行尝试,如果有其他在 ViewBinding 布局中的实现方式,欢迎留言告知我
DataBinding 的使用
因为涉及到的场景比较多,为了减少篇幅,我只列出来核心部分,如果之前从来没有用过,这里只需要知道 DataBinding 的门槛比 Kotlin 合成方法要高即可。
需要给布局文件添加 layout 标签
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout...>
...
</LinearLayout
</layout>
在 Activity 中使用
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.lifecycleOwner = this
setContentView(binding.root)
}
在 Fragment 中使用
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
val binding = FragmentViewBindBinding.inflate(inflater,container,false)
binding.lifecycleOwner = this
return binding.root
}
在 Adapter 中的使用
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
val bidning:RecycleItemProductBinding = DataBindingUtil.bind(view)
}
在 Dialog 中使用
override fun onCreate(savedInstanceState: Bundle?) {
binding = DataBindingUtil.inflate(LayoutInflater.from(context), R.layout.dialog_data_binding, null, false)
setContentView(binding.root)
}
include 标签的使用
include
标签不带 merge
标签,需要给 include
标签添加 id, 直接使用 id 即可。
<include
android:id="@+id/includeData"
layout="@layout/layout_include_data_item"/>
val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.includeData.includeTvTitle.setText("通过代码设置 include layout 的控件")
include
标签带 merge
标签,注意这里和 ViewBinding 用法不一样,给 include
标签添加 id,在 DataBinding 中可以直接使用,在 ViewBinding 中则不行。
<include
android:id="@+id/includeDataMerge"
layout="@layout/layout_merge_data_item"/>
val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.includeDataMerge.mergeTvTitle.setText("通过代码设置 merge layout 的控件")
ViewStub 标签的使用
给 ViewStub
标签添加 id, 在 DataBinding 中可以直接使用 id 即可。
<ViewStub
android:id="@+id/stub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout="@layout/view_stub" />
binding.stub.setOnInflateListener { stub, inflated ->
// ViewBinding
val viewStub: ViewStubBinding = ViewStubBinding.bind(inflated)
viewStub.tvTitle.setText("使用 ViewStub 加载 ViewBinding 布局")
}
binding.stub.setOnInflateListener { stub, inflated ->
// DataBinding
val dataViewStub: ViewStubDataBinding = DataBindingUtil.bind(inflated)!!
dataViewStub.tvTitle.setText("使用 ViewStub 加载 DataBinding 布局")
}
if (!binding.stub.isInflated) {
binding.stub.viewStub!!.inflate()
}
正如你所见,在 Ativity
、 Fragment
、 Dialog
、 Adapter
、 include
、 merge
、 ViewStub
等等场景中,使用 ViewBinding 或者 DataBinding 都要进行不同的处理,相比于 Kotlin 合成方法,这使用门槛太高了。
那么能不能用一种方法,可以统一这些初始化方案,在 Kotlin 中仅仅需要一行代码即可实现 DataBinding 和 ViewBinding。
一行代码
如果在每个场景中都需要手动进行不同的处理,这样的成本是非常大的,因此我推出了一个新库 Binding ,Binding 结合 Kotlin 委托属性,统一封装了 DataBinding 和 ViewBinding 不同的处理, 提供了简单的 API 如下所示。
ViewBinding 中的使用
val binding: ActivityViewBindBinding by viewbind()
DataBinding 中的使用
val binding: ActivityDataBindBinding by databind(R.layout.activity_data_bind)
或者
val binding: ActivityDataBindBinding by databind()
正如你所见,只需要简单的几个 API 即可实现上述所有场景,我们先来介绍一下 Binding。
Binding 未来的规划提供通用的 findViewById
解决方案,因技术的迭代更新从 butterknife
、 DataBinding
、 Kotlin 合成方法(Synthetic 视图)到现在 ViewBinding , 未来也有可能出现新的技术,无论技术怎么变化,只要 Binding 对外的使用保持不变,只需要更新 Binding ,即可完成迁移。
Binding 具有以下优点:
提供了很多实战案例包含 Ativity
、Fragment
、Dialog
、Adapter
、include
、merge
、ViewStub
、Navigation
、 数据双向绑定 等等场景简单的 API 只需要一行代码即可实现 DataBinding 或者 ViewBinding 支持在 Activity
、AppCompatActivity
、FragmentActivity
、Fragment
、Dialog
中的使用 DataBinding 或者 ViewBinding支持在 ListAdapter
、PagedListAdapter
、PagingDataAdapter
、RecyclerView.Adapter
中的使用 DataBinding 或者 ViewBinding支持在 Navigaion Fragment 管理框架、 BottomSheetDialogFragment 等等场景中使用 DataBinding 和 ViewBinding 避免大量的模板代码 避免内存泄露,具有生命周期感知能力,当生命周期处于 onDestroyed()
时会自动销毁数据
接下来我们一起来分析一下如何在项目中使用 Binding,将下列代码添加在模块级 build.gradle
文件内,并且需要开启 DataBinding 或者 ViewBinding。
dependencies {
implementation 'com.hi-dhl:binding:1.0.7'
}
在 Activity
、AppCompatActivity
、FragmentActivity
中使用,添加 by viewbind()
或者 by databind(R.layout.activity_main)
即可,示例如下所示。
class MainActivity : AppCompatActivity() {
// DataBinding
val binding: ActivityMainBinding by databind(R.layout.activity_main)
// ViewBinding
val binding: ActivityMainBinding by viewbind()
}
在 Fragment
中提供了两种方式:
方式一:在 onCreateView
中使用,这种方式适用于所有使用Fragment
的场景方式二:在 onViewCreated
中使用
方式一:
class FragmentNav1 : Fragment(R.layout.fragment_main) {
// DataBinding
val binding: FragmentMainBinding by databind()
// ViewBinding
val binding: FragmentMainBinding by viewbind()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return binding.root
}
}
方式二,需要注意以下几点:
不能在 Navigaion Fragment
和BottomSheetDialogFragment
中使用在其他 Fragment 场景中,如果使用 方式二
界面不显示,改用方式一
即可解决
class FragmentNav1 : Fragment(R.layout.fragment_main) {
// DataBinding
val binding: FragmentMainBinding by databind()
// ViewBinding
val binding: FragmentMainBinding by viewbind()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.apply { textView.setText("Binding") }
}
}
在 Dialog
中使用方式如下所示。
class AppDialog(context: Context) : Dialog(context, R.style.AppDialog) {
// DataBinding
val binding: DialogAppBinding by databind(R.layout.dialog_data_binding)
// ViewBinding
val binding: DialogAppBinding by viewbind()
}
或者添加具有生命周期感知的 Dialog
。
class AppDialog(context: Context,lifecycle: Lifecycle) : Dialog(context, R.style.AppDialog) {
// DataBinding 监听生命周期
val binding: DialogAppBinding by databind(R.layout.dialog_data_binding, lifecycle)
// ViewBinding 监听生命周期
val binding: DialogAppBinding by viewbind(lifecycle)
}
在 Adapter 中使用 DataBinding 和 ViewBinding,只需要在 ViewHolder 中添加 by viewbind()
或者 by databind()
即可,示例如下所示。
class ProductViewHolder(view: View) : RecyclerView.ViewHolder(view) {
// DataBinding
val binding: RecycleItemProductBinding by databind()
// ViewBinding
val binding: RecycleItemProductHeaderBinding by viewbind()
}
扩展方法,支持 DataBinding 初始化的时候绑定数据。
val binding: ActivityDataBindBinding by databind(R.layout.activity_data_bind) {
val account = Account()
account.name = "test"
this.account = account
}
上面只是常见的几种用法,当然还有更多实战案例(include
、 merge
、 ViewStub
、 Navigation
、 数据双向绑定 等等)已经上传到 GitHub 欢迎前去仓库 Binding
查看。
https://github.com/hi-dhl/Binding
这篇文章可以理解为对之前的文章 Kotlin 插件的落幕,ViewBinding 的崛起 的一个补充,从使用的角度分析了 DataBinding 和 ViewBinding 不同之处,同时也介绍了如何用更简单的方式实现 DataBinding 和 ViewBinding。
全文到这里就结束了,如果有帮助欢迎 收藏 、点赞 、分享 、在看 就是对我最大的鼓励
代码不止,文章不停
欢迎关注我,持续分享最新的技术
推荐阅读:
最后推荐我一直在更新维护的项目和网站:
全新系列视频:现代 Android 开发 (MAD) 技巧系列教程
https://madskills.hi-dhl.com最新的 AndroidX Jetpack 相关组件的实战项目 以及 原理分析的文章
https://github.com/hi-dhl/AndroidX-Jetpack-PracticeLeetCode / 剑指 offer / 国内外大厂面试题 / 多线程 题解,语言 Java 和 kotlin,包含多种解法、解题思路、时间复杂度、空间复杂度分析
剑指 offer:https://offer.hi-dhl.com
LeetCode:https://leetcode.hi-dhl.com最新 Android 10 源码分析系列文章
https://github.com/hi-dhl/Android10-Source-Analysis一系列国外的技术文章,每篇文章都会有译者思考部分,对原文的更加深入的分析
https://github.com/hi-dhl/Technical-Article-Translation「为互联网人而设计,国内国外名站导航」涵括新闻、体育、生活、娱乐、设计、产品、运营、前端开发、Android 开发等等网址
https://site.51git.cn
致力于分享一系列最新技术原创文章
长按二维码即可关注
我知道你在看哟