一、概述
背景:在开发Android app时,由于总是要去写一大堆的findViewById,找控件的代码。显得项目臃肿,而且枯燥又没什么意义。所以我们使用更好的方式去处理这些问题。
二、方案
- 1、我们之前经常使用的ButterKnife这类第三方控件,专门用于对findViewById的用法进行简化,,但是ButterKnife还是通过注解的方式来让控件与资源id之间进行操作绑定,所以使用起来并不算是非常便捷。
- 2、随着kotlin的普及,我们会使用到kotlin-android-extensions这样的插件。只需要在在你项目工程模块的build.gradle中加入以下配置:
apply plugin: 'kotlin-android-extensions'
比如说这里有一个布局文件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">
<TextView
android:id="@+id/tv_show
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
那么借助kotlin-android-extensions插件,我们使用如下代码来完成控件的赋值操作(只需要直接引用定义的控件id即可):
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tv_show.text = "Hello"
}
}
三、升级方案
在升级了Android Studio 4.1之后,发现构建项目的时候Android Studio已经不会自动帮我们引入kotlin-android-extensions插件了,需要自己手动去添加才能使用,并且Google明确地告诉我们,kotlin-android-extensions插件已被废弃,现在推荐使用ViewBinding来进行替代。 废除提示如下:
废弃原因:
实际上kotlin-android-extensions插件会帮我们生成一个_$_findCachedViewById()函数。在这个函数中首先会尝试从一个HashMap中获取传入的资源id参数所对应的控件实例缓存,如果还没有缓存的话,就调用findViewById()函数来查找控件实例,并写入HashMap缓存当中。这样当再次获取相同控件实例的话,就可以直接从HashMap缓存中获取了。
然而这种实现原理同时也暴露出来了一些问题。
每个Activity都需要使用一个额外的HashMap数据结构来存储所有控件的实例,无形中增加了一些内存的开支。
所以为了提供运行效率,Google推荐使用ViewBinding的方式进行处理
四、ViewBinding的应用
一、引入配置
ViewBinding总体来说其实非常简单,它的目的只有一个,就是为了避免编写findViewById,这和它另外一个非常复杂的兄弟DataBinding相比有明显的区别。
要想使用ViewBinding需要注意两件事。
- 第一,确保你的Android Studio是3.6或更高的版本。
- 第二,在你项目工程模块的build.gradle中加入以下配置:
android {
...
buildFeatures {
viewBinding true
}
}
二、在Activity中的使用
一旦启动了ViewBinding功能之后,Android Studio会自动为我们所编写的每一个布局文件都生成一个对应的Binding类。
Binding类的命名规则是将布局文件按驼峰方式重命名后,再加上Binding作为结尾。
例如:我们定义了一个activity_main.xml布局,那么与它对应的Binding类就是ActivityMainBinding。
但是如果有些布局文件,你不希望为它生成对应的Binding类,可以在该布局文件的根元素位置加入如下声明:
<LinearLayout
...
tools:viewBindingIgnore="true">
...
</LinearLayout>
动态代码赋值:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.textView.text = "Hello"
}
}
- 1.先通过
ActivityMainBinding.inflate(layoutInflater)
方法获取对应的ViewBinding实例 - 2.然后使用
setContentView()
将binding.root加载到布局文件中 - 3.最后通过binding实例找到指定控件指定属性即可使用
三、在Fragment中的使用
我们有一个布局文件叫fragment_main.xml,那么启用ViewBinding功能之后,则会生成一个与其对应的FragmentMainBinding类,动态赋值如下:
class MainFragment : Fragment() {
private var binding: FragmentMainBinding? = null
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
}
}
由于我们是在onCreateView()函数中加载的布局,那么理应在与其对应的onDestroyView()函数中对binding变量置空,从而保证binding变量的有效生命周期是在onCreateView()函数和onDestroyView()函数之间。
四、在Adapter中使用
假设我们定义了item_rv.xml来作为RecyclerView子项的布局: 然后编写如下RecyclerView Adapter来加载和显示这个子项布局:
class RvAdapter(val list: List<Data>) : RecyclerView.Adapter<RvAdapter.ViewHolder>() {
inner class ViewHolder(binding: ItemRvBinding) : RecyclerView.ViewHolder(binding.root) {
val image: ImageView = binding.image
val text: TextView = binding.text
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = ItemRvBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val data = list[position]
holder.image.setImageResource(data.imageId)
holder.text.text = data.name
}
override fun getItemCount() = list.size
}