ViewBinding是android jetpack的一个特性,ViewBinding总体来说其实非常简单,它的目的只有一个,就是为了避免编写findViewById。因为他会为每个 XML 布局文件生成一个绑定类。绑定类的实例包含在相应布局xml文件中具有 ID 的所有视图的直接引用。
1、提高编译速度 因为不需要在编译期处理大量的annotation(注释的解析)的逻辑。之前android 大名鼎鼎的bufferknife 也是为了解决代码中大量的findviewbyid的问题,但是缺点是增加了编译速度,因为bufferknife 编译时需要处理大量的annotation的逻辑。连butterknife 的作者已经宣布不维护Butter Knife,推荐使用ViewBinding了。
2、就是为了避免编写findViewById(直接通过bind引用即可)。
3、减少控件类型转换带来的错误(防止你用比如text的id给Button对象带来的运行期错误)。
要想使用ViewBinding需要注意两件事。第一,确保你的Android Studio是3.6或更高的版本。第二,在你项目工程模块的build.gradle中加入以下配置:
android {
...
buildFeatures {
viewBinding true
}
}
这样准备工作就完成了。接下来我会从Activity、Fragment、Adapter、引入布局这4个方面,分别讨论ViewBinding的用法。
一旦启动了ViewBinding功能之后,Android Studio会自动为我们所编写的每一个布局文件都生成一个对应的Binding类。Binding类的命名规则是将布局文件按驼峰方式重命名后,再加上Binding作为结尾。
比如说,当创建一个activity_main.xml布局,那么与它对应的Binding类就是ActivityMainBinding。因为他生成的是类,所以我们就可以去直接去引用使用。
假设有一个布局是 activity_main.xml
<LinearLayout
xmlns:tools="http://schemas.android.com/tools"
...>
<Button
android:text="这是按钮"
android:id="@+id/test_view_binding"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
在Activity如何引用
class MainActivity : Activity() {
//首先声明全局binding变量
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//再通过生成的binding去加载该布局
binding = ActivityMainBinding.inflate(layoutInflater)
//调用Binding类的getRoot()函数可以得到activity_main.xml中根元素的实例
val view = binding.root
//将根视图传递到 setContentView(),使其成为屏幕上的活动视图
setContentView(view)
//使用binding实例获取到控件
binding.testViewBinding.text = "button" //更改id为testViewBinding的内容
}
}
注意,Kotlin声明的变量都必须在声明的同时对其进行初始化。而这里我们显然无法在声明全局binding变量的同时对它进行初始化,所以这里又使用了lateinit关键字对binding变量进行了延迟初始化。
在Fragment中使用ViewBinding和在Activity基本是一样的,但是也有一些不同。主要是因为他们的生命周期就是不同的。(这里布局文件省略,就叫fragment_main.xml吧)
class MainFragment : Fragment() {
private var _binding: FragmentMainBinding? = null
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = FragmentMainBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
这里Fragment加载布局就是用的inflate去加载的。和正常Fragment加载布局如出一辙。
大家可能发现了为什么不用lateinit关键字对binding变量进行延迟初始化。
这是因为:binding变量只有在onCreateView与onDestroyView才是可用的。因为我们fragment的生命周期和activity的不同,fragment 可以超出其视图的生命周期,如果不将这里置为空,有可能引起内存泄漏。所以我们要在onCreateView中创建,onDestroyView置空。
RecycleView的子项布局(fruit_item.xml)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
.....>
<ImageView
android:id="@+id/fruitImage"
..... />
<TextView
..... />
</LinearLayout>
Adapter中如何去使用
class FruitAdapter(val fruitList: List<Fruit>) : RecyclerView.Adapter<FruitAdapter.ViewHolder>() {
//Myholder接受FruitItemBinding参数,RecyclerView.ViewHolder接受的是一个View,通过这个binding.root返回一个root
inner class ViewHolder(binding: FruitItemBinding) : RecyclerView.ViewHolder(binding.root) {
val fruitImage: ImageView = binding.fruitImage
val fruitName: TextView = binding.fruitName
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
//首先调用FruitItemBinding的inflate函数去加载fruit_item.xml的布局
val binding = FruitItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val fruit = fruitList[position]
//通过holder直接使用它自己的成员变量
holder.fruitImage.setImageResource(fruit.imageId)
holder.fruitName.text = fruit.name
}
override fun getItemCount() = fruitList.size
}
引入布局一般有两种方式,include和merge。假设我们有如下引用title_bar.xml布局,主布局activity_main.xml。
被引入布局title_bar.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
.....>
<Button
android:text="include"
android:id="@+id/test_include"
....../>
</LinearLayout>
主布局activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
.....>
<!--在include标签中添加id属性-->
<include
android:id="@+id/title_bar"
//注意要写id不然ViewBinding是关联不到title_bar.xml中的控件的。
layout="@layout/title_bar"/>
...
</LinearLayout>
Activity中我们怎么去引用呢
class MainActivity : Activity() {
//首先声明变量
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
binding = ActivityMainBinding.inflate(layoutInflater)
//调用Binding类的getRoot()函数可以得到activity_main.xml中根元素的实例
val view = binding.root
//将根视图传递到 setContentView(),使其成为屏幕上的活动视图
setContentView(view)
//直接使用include标签的id,然后再根据include的id引用include布局里面的id
binding.titleBar.testInclude.text = "hello"
}
}
merge:使用merge标签引入的布局在某些情况下可以减少一层布局的嵌套,而更少的布局嵌套通常就意味着更高的效率。具体merge用处请看:Android布局优化技巧
好了先看跟普通引入有什么区别吧:
首先我们在include的时候不可以再给他Id了,因为merge标签并不是一个布局,所以我们无法像刚才那样在include的时候给它指定一个id。不然会报错崩溃。
merge标签布局title_bar.xml
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<Button
android:text="include"
android:id="@+id/test_include"
....../>
</merge>
引入布局activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
.....>
<!--在include标签中添加id属性-->
<include
//android:id="@+id/title_bar" //因为merge不能用id
layout="@layout/title_bar"/>
...
</LinearLayout>
那么问题来了没有id了,ViewBinding怎么去绑定呢,一起来看看吧。
Activity利用ViewBinding绑定
private lateinit var binding: ActivityMainBinding
//声明变量
private lateinit var titleBarBinding: TitleBarBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
//调用TitleBarBind的bind函数让title_bar.xml和我们的activity_main.xml关联起来
titleBarBinding = TitleBarBinding.bind(binding.root)
val view = binding.root
setContentView(view)
//直接使用titlrBarBinding变量引用控件
titleBarBinding.testInclude.text = "button"
}
在onCreate()函数中,我们调用TitlebarBinding.bind()函数,让titlebar.xml布局和activity_main.xml布局能够关联起来。
接下来的事情就很简单了,直接使用titlebarBinding变量就可以引用到titlebar.xml中定义的各个控件了。
好了,这大概就是关于ViewBinding的所有内容了。下一篇文章我会讲解DataBinding的使用。
最后呢我还会把ViewBinding(视图绑定)和DataBinding(数据绑定)的区别讲解给大家。