竟然如此简单,DataBinding 和 ViewBinding

竟然如此简单,DataBinding 和 ViewBinding

  • 如果评论区没有及时回复,欢迎来公众号:ByteCode 咨询
  • 公众号:ByteCode。致力于分享最新技术原创文章,涉及 Kotlin、Jetpack、算法、译文、系统源码相关的文章

前言

首先祝小伙伴们新年快乐,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.6Android 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](https://github.com/hi-dhl/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](https://github.com/hi-dhl/Binding)。

Binding 未来的规划提供通用的 `findViewById` 解决方案,因技术的迭代更新从 `butterknife` 、 `DataBinding` 、 Kotlin 合成方法(Synthetic 视图)到现在 ViewBinding , 未来也有可能出现新的技术,无论技术怎么变化,只要 Binding 对外的使用保持不变,只需要更新 Binding ,即可完成迁移。

**[Binding](https://github.com/hi-dhl/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](https://github.com/hi-dhl/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) 查看。

GitHub 仓库: [https://github.com/hi-dhl/Binding](https://github.com/hi-dhl/Binding)

这篇文章可以理解为对之前的文章 [Kotlin 插件的落幕,ViewBinding 的崛起](https://juejin.cn/post/6905942568467759111)  的一个补充,从使用的角度分析了 DataBinding 和 ViewBinding 不同之处,同时也介绍了如何用更简单的方式实现 DataBinding 和 ViewBinding。


致力于分享一系列 Android 系统源码、逆向分析、算法、翻译、Jetpack 源码相关的文章,在技术的道路上一起前进

Android10 源码分析

正在写一系列的 Android 10 源码分析的文章,了解系统源码,不仅有助于分析问题,在面试过程中,对我们也是非常有帮助的,如果你同我一样喜欢研究 Android 源码,可以关注我 GitHub 上的 Android10-Source-Analysis

算法题库的归纳和总结

由于 LeetCode 的题库庞大,每个分类都能筛选出数百道题,由于每个人的精力有限,不可能刷完所有题目,因此我按照经典类型题目去分类、和题目的难易程度去排序。

  • 数据结构: 数组、栈、队列、字符串、链表、树……
  • 算法: 查找算法、搜索算法、位运算、排序、数学、……

每道题目都会用 Java 和 kotlin 去实现,并且每道题目都有解题思路,如果你同我一样喜欢算法、LeetCode,可以关注我 GitHub 上的 LeetCode 题解:Leetcode-Solutions-with-Java-And-Kotlin

精选国外的技术文章

目前正在整理和翻译一系列精选国外的技术文章,不仅仅是翻译,很多优秀的英文技术文章提供了很好思路和方法,每篇文章都会有译者思考部分,对原文的更加深入的解读,可以关注我 GitHub 上的 Technical-Article-Translation

评论