ViewBinding基本使用

164 阅读4分钟

一、概述

背景:在开发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来进行替代。 废除提示如下:

图1

废弃原因:
实际上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

}

五、对引入布局使用