Android ViewBinding 浅析

ViewBinding 是什么

2020年的3月份 巨佬 JakeWharton 开源的 butterknife 被官宣 停止维护,在github 上 说明

Attention: This tool is now deprecated. Please switch to view binding. Existing versions will continue to work, obviously, but only critical bug fixes for integration with AGP will be considered. Feature development and general bug fixes have stopped.

翻译过来就是,不维护了,推荐你使用 view binding ,那么 view binding 是什么呢

官网中,这么介绍

通过视图绑定功能,您可以更轻松地编写可与视图交互的代码。在模块中启用视图绑定之后,系统会为该模块中的每个 XML 布局文件生成一个绑定类。绑定类的实例包含对在相应布局中具有 ID 的所有视图的直接引用。在大多数情况下,视图绑定会替代 findViewById。

简而言之 就是用来查找view

ViewBinding 有什么作用

就想刚刚说的 ,他就是为了替换 findViewById。 的。那么 原来的 findViewById。 有什么问题么?干嘛就要替换了他?

总结了以下几个原因:

  • 过于冗余

findViewById 对应所有的View 都需要书写以下 findViewById(R.id.xxx) 的方法

  • 不安全

所谓的不安全就是分为两个 首先是空类型的不安全,findViewById 又可能返回为null,导致程序异常
第二: 强转的不安全,findViewById。 将 对应的id 需要强转成对应的View 例如

1
TextView tv = findViewById(R.id.textview);

一旦我的类型给错了,就会出现异常,比如将textview 错强转成 ImageView

为什么不用其他替换方案

总结一下,所谓的其他替代方案,无非就那么几种,

  • Butter Knife(Kotter Knife)
  • Kotlin Android Extensions
  • data bind

Butter Knife

先说一下 Butter Knife(Kotter Knife) ,在文章最开始 就说明了,这个已经被巨佬 JakeWharton 官宣 不在维护,而且也推崇 view bind,作为一个时代的产物,奶油刀也带给我们很多方便,
而他的缺点其实也是有的,依然有着 不安全的属性,同时,使用了 Butter Knife 代码量其实也少不了多少,只是使用注解的方式,显得优雅一些

Kotlin Android Extensions

至于 Kotlin Android Extensions ,大家写Kotlin 肯定使用过

导入 这个插件

1
2
3
plugins {
id 'kotlin-android-extensions'
}

然后通过 导入对应的xml 文件 就可直接使用对应的 资源

1
import kotlinx.android.synthetic.main.activity_main.*
1
2
3
4
5
<LinearLayout>
...
<TextView id="@+id/text">
...
</LinearLayout>
1
2
3
4
5
6
7
8
9
10
import kotlinx.android.synthetic.main.activity_main.*

class JetPackAct : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
text.text = "hello world"
}
}

其实我感觉还是挺好用的。然而即使这么好用的东西。 在Kotlin 1.4.20-M2中,JetBrains废弃了Kotlin Android Extensions编译插件。虽然现在的稳定版还能够使用,不过google 大法好,还是不建议继续使用。

data bind

官网在 与数据绑定的对比 中也详细的对比了两者

view bind 优点

  • 更快的编译速度:view bind 不需要处理注释,因此编译时间更短。
  • 易于使用:view bind不需要特别标记的 XML 布局文件,因此在应用中采用速度更快。在模块中启用视图绑定后,它会自动应用于该模块的所有布局。

相对于 data bind ; view bind 缺点

视图绑定不支持布局变量或布局表达式,因此不能用于直接在 XML 布局文件中声明动态界面内容。
视图绑定不支持双向数据绑定

如何使用view bind

关于View bind 关于他的用法 其实很简单,官网也有详细的说明,这里也简单说一下,不过依然建议去看一下官网 ,毕竟官网才是最好的文档。

启动ViewBinding

Android Studio 3.6 Canary 11 及更高版本中可用

1
2
3
4
5
6
android {
...
viewBinding {
enabled = true
}
}

从Android Gradle Plugin 4.0.0-alpha05这里开始,有一个称为buildFeatures启用构建功能的新块。

1
2
3
4
5
6
7
android {
...
buildFeatures {
// 启用 viewBinding
viewBinding = true
}
}

为某个模块启用视图绑定功能后,系统会为该模块中包含的每个 XML 布局文件生成一个绑定类。每个绑定类均包含对根视图以及具有 ID 的所有视图的引用。系统会通过以下方式生成绑定类的名称:将 XML 文件的名称转换为驼峰式大小写,并在末尾添加“Binding”一词。

例如,假设某个布局文件的名称为 result_profile.xml

1
2
3
4
5
6
<LinearLayout ... >
<TextView android:id="@+id/name" />
<ImageView android:cropToPadding="true" />
<Button android:id="@+id/button"
android:background="@drawable/rounded_button" />
</LinearLayout>

所生成的绑定类的名称就为 ResultProfileBinding。此类具有两个字段:一个是名为 name 的 TextView,另一个是名为 button 的 Button。该布局中的 ImageView 没有 ID,因此绑定类中不存在对它的引用。

每个绑定类还包含一个 getRoot() 方法,用于为相应布局文件的根视图提供直接引用。在此示例中,ResultProfileBinding 类中的 getRoot() 方法会返回 LinearLayout 根视图。

Activity 中使用

1
2
3
4
5
6
7
8
9
10
11
12
private lateinit var binding: ResultProfileBinding

override fun onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
binding = ResultProfileBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)

binding.name.text = viewModel.name
binding.button.setOnClickListener { viewModel.userClicked() }

}

Fragmeng 中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

private var _binding: ResultProfileBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = ResultProfileBinding.inflate(inflater, container, false)
val view = binding.root
return view
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}

Fragment 的存在时间比其视图长。请务必在 Fragment 的 onDestroyView() 方法中清除对绑定类实例的所有引用。