Kotlin中ViewBinding替换Kotlin-android-extensions

Kotlin-android-extensions被废弃

Google 不推荐使用extensions插件,而推荐使用viewBinding 来代替
Google明确地告诉我们,kotlin-android-extensions插件已被废弃,现在推荐使用ViewBinding来进行替代。
如何使用ViewBinding来替代kotlin-android-extensions插件。

为什么会被废弃

kotlin-android-extensions插件只能支持Kotlin语言,而无法支持Java语言。当然这个我认为并不是主要原因,因为现在Google开发的各种新技术都在全面兼容Kotlin,而不再怎么去考虑Java了,如协程、Jetpack Compose等。

什么是ViewBinding

ViewBinding总体来说其实非常简单,它的目的只有一个,就是为了避免编写findViewById,这和它另外一个非常复杂的兄弟DataBinding相比有明显的区别。
要想使用ViewBinding需要注意两件事。第一,确保你的Android Studio是3.6或更高的版本。第二,在你项目工程模块的build.gradle中加入以下配置

android {...buildFeatures {viewBinding true}
}

在Activity中使用ViewBinding

一旦启动了ViewBinding功能之后,Android Studio会自动为我们所编写的每一个布局文件都生成一个对应的Binding类。
Binding类的命名规则是将布局文件按驼峰方式重命名后,再加上Binding作为结尾。
比如说,前面我们定义了一个activity_main.xml布局,那么与它对应的Binding类就是ActivityMainBinding。
当然,如果有些布局文件你不希望为它生成对应的Binding类,可以在该布局文件的根元素位置加入如下声明:

<LinearLayoutxmlns:tools="http://schemas.android.com/tools"...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"
}
}

ViewBinding用法很简单。首先我们要调用activity_main.xml布局文件对应的Binding类,也就是ActivityMainBinding的inflate()函数去加载该布局,inflate()函数接收一个LayoutInflater参数,在Activity中是可以直接获取到的。
接下来就更加简单了,调用Binding类的getRoot()函数可以得到activity_main.xml中根元素的实例,调用getTextView()函数可以获得id为textView的元素实例。
那么很明显,我们应该把根元素的实例传入到setContentView()函数当中,这样Activity就可以成功显示activity_main.xml这个布局的内容了。然后获取TextView控件的实例,并给它设置要显示的文字即可。
当然,如果你需要在onCreate()函数之外的地方对控件进行操作,那么就得将binding变量声明成全局变量,写法如下:

class MainActivity : AppCompatActivity() {private lateinit var binding: ActivityMainBindingoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding = ActivityMainBinding.inflate(layoutInflater)setContentView(binding.root)binding.textView.text = "Hello"}}

在Fragment中使用ViewBinding

在Fragment中使用ViewBinding和在Activity基本是一样的。
假设我们有一个布局文件叫fragment_main.xml,那么启用ViewBinding功能之后,则必然会生成一个与其对应的FragmentMainBinding类。
如果我们想要在MainFragment中去显示这个布局,就可以这样写:

class MainFragment : Fragment() {private var _binding: FragmentMainBinding? = nullprivate 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}}

在Adapter中使用ViewBinding

 class FruitAdapter(val fruitList: List<Fruit>) : RecyclerView.Adapter<FruitAdapter.ViewHolder>() 
{
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 {
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.fruitImage.setImageResource(fruit.imageId)
holder.fruitName.text = fruit.name}
override fun getItemCount() = fruitList.size
}

这段代码的核心基本都在onCreateViewHolder()函数和ViewHolder当中。
首先,我们在onCreateViewHolder()函数中调用FruitItemBinding的inflate()函数去加载fruit_item.xml布局文件,这和ViewBinding在Fragment中的用法是一模一样的。
接下来需要改造ViewHolder,让其构造函数接收FruitItemBinding这个参数。但是注意,ViewHolder的父类RecyclerView.ViewHolder它只会接收View类型的参数,因此我们需要调用binding.root获得fruit_item.xml中根元素的实例传给RecyclerView.ViewHolder。
这样,我们就不需要再使用findViewById()函数来查找控件实例了,而是调用binding.fruitImage和binding.fruitName就可以直接引用到相应控件的实例。
这就是ViewBinding在Adapter中的用法。

对引入布局使用ViewBinding

引入布局一般有两种方式,include和merge。
接下来我们开始分别学习如何在include和merge的布局中使用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" /><TextViewandroid:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Title"android:textSize="20sp" /><Buttonandroid: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>

这种写法虽然的确可以将titlebar.xml引入到activity_main.xml布局当中,但问题是,你会发现ViewBinding是关联不到titlebar.xml中的控件的。
那么如何解决这个问题呢?很简单,我们只需要在include的时候给被引入的布局添加一个id,如下所示

 <?xml version="1.0" encoding="utf-8"?>
<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" />...
</LinearLayout>

然后,在MainActivity中,我们即可通过如下的写法引用到titlebar.xml中定义的控件:

 class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBindingoverride fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.titleBar.title.text = "Title"
binding.titleBar.back.setOnClickListener {}binding.titleBar.done.setOnClickListener {}}}

接下来我们再来看一下merge。merge和include最大的区别在于,使用merge标签引入的布局在某些情况下可以减少一层布局的嵌套,而更少的布局嵌套通常就意味着更高的效率。
比如说我们对titlebar.xml进行如下修改:

<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标签,这就表示当有任何一个地方去include这个布局时,会将merge标签内包含的内容直接填充到include的位置,不会再添加任何额外的布局结构。
但是很遗憾,如果使用这种写法的话,运行程序将会直接崩溃。因为merge标签并不是一个布局,所以我们无法像刚才那样在include的时候给它指定一个id。
那么这种情况下应该怎么使用ViewBinding呢?首先为了避免崩溃,我们应该将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">
<includelayout="@layout/titlebar" />
</LinearLayout>

然后修改MainActivity中的代码,如下所示:

  class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var titlebarBinding: TitlebarBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)binding = ActivityMainBinding.inflate(layoutInflater)
titlebarBinding = TitlebarBinding.bind(binding.root)
setContentView(binding.root)
titlebarBinding.title.text = "Title"
titlebarBinding.back.setOnClickListener {}titlebarBinding.done.setOnClickListener {}}}

可以看到,这里我们又定义了一个titlebarBinding变量。很明显,TitlebarBinding就是Android Studio根据我们的titlebar.xml布局文件自动生成的Binding类。
在onCreate()函数中,我们调用TitlebarBinding.bind()函数,让titlebar.xml布局和activity_main.xml布局能够关联起来。
这是学习viewBinding的一点心得,与诸位共勉

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容