前言
公众号:ByteCode,致力于分享最新技术原创文章,涉及 Kotlin、Jetpack、译文、系统源码、 LeetCode / 剑指 Offer / 多线程 / 国内外大厂算法题 等等一系列文章。
首先祝小伙伴们新年快乐,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
因为没有找到比较权威的资料证明,这里建议小伙们直接在项目 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 查看。
GitHub 仓库: github.com/hi-dhl/Bind…
这篇文章可以理解为对之前的文章 Kotlin 插件的落幕,ViewBinding 的崛起 的一个补充,从使用的角度分析了 DataBinding 和 ViewBinding 不同之处,同时也介绍了如何用更简单的方式实现 DataBinding 和 ViewBinding。
全文到这里就结束了,如果有帮助 点个赞 就是对我最大的鼓励
代码不止,文章不停
欢迎关注公众号:ByteCode,持续分享最新的技术
最后推荐我一直在更新维护的项目和网站:
- 全新系列视频:现代 Android 开发 (MAD) 技巧系列教程:在线查看
- 计划建立一个最全、最新的 AndroidX Jetpack 相关组件的实战项目 以及 相关组件原理分析文章,正在逐渐增加 Jetpack 新成员,仓库持续更新,欢迎前去查看:AndroidX-Jetpack-Practice
- LeetCode / 剑指 offer / 国内外大厂面试题 / 多线程 题解,语言 Java 和 kotlin,包含多种解法、解题思路、时间复杂度、空间复杂度分析
- 最新 Android 10 源码分析系列文章,了解系统源码,不仅有助于分析问题,在面试过程中,对我们也是非常有帮助的,仓库持续更新,欢迎前去查看 Android10-Source-Analysis
- 整理和翻译一系列精选国外的技术文章,每篇文章都会有译者思考部分,对原文的更加深入的解读,仓库持续更新,欢迎前去查看 Technical-Article-Translation
- 「为互联网人而设计,国内国外名站导航」涵括新闻、体育、生活、娱乐、设计、产品、运营、前端开发、Android 开发等等网址,欢迎前去查看 为互联网人而设计导航网站
历史文章
- 为数不多的人知道的 Kotlin 技巧以及 原理解析(一)
- 为数不多的人知道的 Kotlin 技巧以及 原理解析(二)
- Jetpack 最新成员 AndroidX App Startup 实践以及原理分析
- Jetpack 成员 Paging3 实践以及源码分析(一)
- Jetpack 成员 Paging3 网络实践及原理分析(二)
- Jetpack成员Paging3获取网络分页数据并更新到数据库中(三)
- Jetpack 成员 Hilt 实践(一)启程过坑记
- Jetpack 成员 Hilt 结合 App Startup(二)进阶篇)进阶篇
- Jetpack 新成员 Hilt 与 Dagger 大不同(三)落地篇
- 全方面分析 Hilt 和 Koin 性能
- 神奇宝贝(PokemonGo) 眼前一亮的 Jetpack + MVVM 极简实战
- Kotlin Sealed 是什么?为什么 Google 都用
- Kotlin StateFlow 搜索功能的实践 DB + NetWork
- 再见吧 buildSrc, 拥抱 Composing builds 提升 Android 编译速度
- [Google] 再见 SharedPreferences 拥抱 Jetpack DataStore
- [译] Google 官方正解是否应该学习 Kotlin
- 官宣 有趣的 Android Studio 版本新方案
- Kotlin 插件的落幕,ViewBinding 的崛起