一、为什么学
在使用Java开发Android程序时,我们总是要写一大堆的findViewById,枯燥又没什么意义,因此针对这个问题也出了很多开源库来解决,例如:Butter Knife(黄油刀)。但是ButterKnife还是要通过注解来让控件与资源id之间进行绑定,并不算是非常方便。我去GitHub上看,发现在库说明的开始位置有下面的提示:

嗯。。。废弃了,已经直接让你转去看view binding了。
在学习kotlin的时候,又学到了kotlin-android-extensions插件,当时用的时候真是感觉像发现了新大陆一样,非常好用。之前AS版本新建Kotlin项目会自动导入这个插件,现在使用的话需要手动加上。
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-android-extensions'
}
导入插件后,用到的地方直接以view id拿就行。例如:
<TextView
android:id="@+id/tv_msg"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tv_msg.text = "Hello"
}
}
但是,当你的gradle升级到新版本的时候,会显示下面的提示。Google明确地告诉我们,kotlin-android-extensions插件已被废弃,现在推荐使用ViewBinding来进行替代。

很奇怪,这么好用的插件为啥要废弃,查了下主要有如下几个原因:
-
内存问题:根据
kotlin-android-extensions插件源码,它使用了一个HashMap来存放所有的id和对应的View的缓存,如果缓存中有需要的View,就直接获取,否则就通过findViewById去创建,并写入HashMap缓存当中,这样当下次再获取相同控件实例的话,就可以直接从HashMap缓存中获取了,这就是它的原理。
额外的HashMap数据结构来存储所有控件的实例,无形中增加了一些内存的开支。参考:kotlin-android-extensions插件也被废弃了?扶我起来 -
资源ID重名:由于
kotlin-android-extensions是通过view的id名直接引用的,所以多个布局间的同名id,就需要手动对import进行重命名处理,而且经常会引用错误的布局文件,导致运行崩溃。 -
Kotlin only:只能在
Kotlin中使用,无法在Java项目中使用。
既然官方主推这个ViewBinding,让我们来学习下。
二、ViewBinding使用
想使用ViewBinding需要注意两件事:
- 第一,确保你的Android Studio是3.6或更高的版本;
- 第二,项目工程模块的build.gradle中加入以下配置:
android {
...
buildFeatures {
viewBinding true
}
}
1、在Activity中使用ViewBinding
一旦启动了ViewBinding功能之后,Android Studio会自动为我们所编写的每一个布局文件都生成一个对应的Binding类。
Binding类的命名规则是将布局文件按驼峰方式重命名后,再加上Binding作为结尾。比如说,前面我们定义了一个activity_main.xml布局,那么与它对应的Binding类就是ActivityMainBinding。同理,如果是activity_login.xml对应的Binding类就是ActivityLoginBinding。
当然,如果有些布局文件你不希望为它生成对应的Binding类,可以在该布局文件的根元素位置加入如下声明:
<LinearLayout
xmlns:tools="http://schemas.android.com/tools"
...
tools:viewBindingIgnore="true">
...
</LinearLayout>
看下在Activity中的使用:
class MainActivity : BaseActivity() {
private lateinit var mBinding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(mBinding.root)
mBinding.forceOffLine.setOnClickListener {
...
}
}
}
是的,就是这么简单,首先获取Binding类,然后把根元素的实例传入到setContentView()函数当中,这样Activity就可以成功显示activity_main.xml这个布局的内容了。后面直接使用mBinding.forceOffLine来获取按钮view就可以了。
2、在Fragment中使用ViewBinding
在Fragment中使用ViewBinding和在Activity基本是一样的,来看下代码:
class MainFragment : Fragment() {
private var mMainBinding: FragmentMainBinding? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
mMainBinding = FragmentMainBinding.inflate(inflater, container, false)
return mMainBinding?.root
}
override fun onDestroyView() {
super.onDestroyView()
mMainBinding = null
}
}
因为mMainBinding在onCreateView 进行了初始化,所以在onDestroyView需要置空,因此mMainBinding需要设置为可空类型,在返回root的时候需要判空。
3、在Adapter中使用ViewBinding
class InfoAdapter(val infoList: List<String>) : RecyclerView.Adapter<InfoAdapter.ViewHolder>() {
private lateinit var mBinding: ItemInfoBinding
//2.继承类中传入binding.root,然后根据binding获取item中的view
inner class ViewHolder(binding: ItemInfoBinding) : RecyclerView.ViewHolder(binding.root) {
val tvInfo: TextView = binding.infos
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
//1.获取ItemInfoBinding
mBinding = ItemInfoBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(mBinding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.tvInfo.text = infoList[position]
}
override fun getItemCount(): Int = infoList.size
}
在Adapter中主要修改两个地方,一个是在onCreateViewHolder中获取ItemInfoBinding,然后传入到ViewHolder中。然后在ViewHolder中根据传入的ItemInfoBinding获取view来替代findViewById。不过每个view也需要一一获取,和之前的findViewById相比,我觉得在Adapter中优化的能力一般。
4、对引入布局使用ViewBinding
- 引入布局include
下面是定义的titlebar.xml布局:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<Button
android:id="@+id/back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:text="Back" />
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Title"
android:textSize="20sp" />
<Button
android:id="@+id/done"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:text="Done" />
</RelativeLayout>
在activity_main.xml中引入这个布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include
layout="@layout/titlebar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
...
</LinearLayout>
此时使用ActivityMainBinding时无法拿到titlebar中的控件的。想拿到控件需要在include的时候给被引入的布局添加一个id,当成一个普通的控件,代码如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include
android:id="@+id/titleBar"
layout="@layout/titlebar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
...
</LinearLayout>
此时可通过mBinding获取到引入布局控件。
class MainActivity : BaseActivity() {
private lateinit var mBinding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(mBinding.root)
mBinding.titleBar.title.setText("talk")
}
}
- 引入布局merge
merge和include最大的区别在于,使用merge标签引入的布局在某些情况下可以减少一层布局的嵌套,而更少的布局嵌套通常就意味着更高的效率。
将上面的布局改下:
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<Button
android:id="@+id/back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:text="Back" />
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Title"
android:textSize="20sp" />
<Button
android:id="@+id/done"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:text="Done" />
</merge>
使用了merge标签,会将merge标签内包含的内容直接填充到include的位置,不会再添加任何额外的布局结构。
但是如果只修改上面的titlebar.xml,程序运行程序将会直接崩溃(编译可以通过)。因为merge标签并不是一个布局,所以我们无法像刚才那样在include的时候给它指定一个id。会报下面错误:
java.lang.NullPointerException: Missing required view with ID: com.jane.demo:id/titlebar
所以需要修改下activity_main.xml去掉指令的id:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include
layout="@layout/titlebar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
...
</LinearLayout>
从上面测试知道,没有id的话就无法拿到引入布局的控件,所以这时需要修改下代码:
class MainActivity : BaseActivity() {
private lateinit var mBinding: ActivityMainBinding
private lateinit var mTitleBarBinding: TitlebarBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = ActivityMainBinding.inflate(layoutInflater)
mTitleBarBinding = TitlebarBinding.bind(mBinding.root)
setContentView(mBinding.root)
mTitleBarBinding.title.setText("Title")
}
}
我们调用TitlebarBinding.bind()函数,让titlebar.xml布局和activity_main.xml布局能够关联起来。
以上是ViewBinding的使用学习,码字不易,如果有帮助到大家请点赞收藏。
Android ViewBinding使用学习
博客介绍了学习Android ViewBinding的原因,指出Java开发Android时绑定控件繁琐,开源库ButterKnife和Kotlin插件存在不足而被废弃。还详细讲解了ViewBinding的使用,包括在Activity、Fragment、Adapter中以及对引入布局的使用方法。
676

被折叠的 条评论
为什么被折叠?



