Android开发学习笔记——ViewBinding

文章目录

  • Android开发学习笔记
    • 基本使用
      • 开启ViewBinding
      • 视图绑定
        • Activity中使用ViewBinding
        • Fragment中使用ViewBinding
        • 自定义View中使用ViewBinding
        • 自定义Dialog中使用ViewBinding
        • Adapter中使用ViewBinding
        • 总结
      • 使用Binding类来操作View
        • include和merge
    • 比较
    • 总结

Android开发学习笔记

在Android开发过程中,当我们需要使用到布局中的组件,为其进行赋值或者设置监听等操作时,我们往往需要先获取到其对象,在最初的Android开发中,我们需要使用findViewById方法,通过组件的id来获取其对象。但是,这种最原始的方法,在xml布局较为复杂时,布局中可能存在十多个甚至几十个,此时如果我们仅仅使用findViewById来获取View对象的话,就会多写很多重复代码,而且显得臃肿和不优雅。而作为开发者,我们当然需要尽量避免这种情况的发生,为了减少视图绑定的工作量和重复代码,ButterKnifeDataBindingAndroid Kotlin Extensions等应运而生。

但是这些方法,或多或少都存在这一些缺陷,而google在AndroidStudio 3.6 版本后推出了ViewBinding,一方面可以让代码更加简洁并且提高编译速度防止空指针。另一方面AndroidStudio是支持ViewBinding进行关联互动的,所以让你在代码与xml之间的跳转更方便。

基本使用

首先,让我们来学习下ViewBinding的基本使用。对于ViewBinding而言,其使用步骤主要分为三步,如下:

  • 开启ViewBinding
  • 进行视图绑定并获取绑定类的实例
  • 使用绑定类实例引用相关View对象并进行相关操作

开启ViewBinding

在Android Studio3.6即之后版本,我们可以使用ViewBinding,该功能可按模块启用,如果我们需要在某个模块中启用视图绑定,我们只需要将viewBinding 元素添加到其对应的 build.gradle 文件中,如下所示:

android {
        ...
        viewBinding {
            enabled = true
        }
    }
    

开启ViewBinding后,系统会自动为每个xml布局生成一个对应的Binding类,如果我们在某个布局中不希望使用到ViewBinding,那么,我们只需要在其根View下指定 tools:viewBindingIgnore="true" 即可,如下:


<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".viewbinding.ViewBindingActivity"
    tools:viewBindingIgnore="true">

androidx.constraintlayout.widget.ConstraintLayout>

如此一来,系统在生成绑定类时就会忽略该布局文件。

视图绑定

实际上,对于开发者而言,使用ViewBinding的最重要的一步就是进行视图绑定了,只有完成视图绑定后,我们就可以使用其绑定类的实例来引用View对象了。

为某个模块启用视图绑定功能后,系统会为该模块中包含的每个 XML 布局文件生成一个绑定类。每个绑定类均包含对根视图以及具有 ID 的所有视图的引用。系统会通过以下方式生成绑定类的名称:将 XML 文件的名称转换为驼峰式大小写,并在末尾添加“Binding”一词。而Binding类中会自动生成xml布局中的View引用,并可以以其id的驼峰形式来获取对应的引用。如:存在一个登录界面其xml布局文件为 activity_login.xml,其中存在一个id为 bt_login的button,那么其对应生成的Binding类就为ActivityLoginBinding ,其中存在一个btLogin的Button对象。通过该对象,我们就可以对布局中的 bt_login 进行操作了。

不过Binding类不能直接进行实例化,那么,我们应该如何进行视图绑定,获取Binding类的实例呢?在ViewBinding中主要存在以下几个方法:

  • inflate(LayoutInflater, ViewGroup?, Boolean)
  • bind(View)
Activity中使用ViewBinding

在系统生成的Binding类中,除了我们xml布局文件中的View对象引用以外,每个Binding类还自带一个root对象,其对应的时布局文件的根View。我们知道,对于Activity的视图绑定,我们都是在onCreate方法中使用setContentView来完成的。实际上使用ViewBinding也是一样,不过,我们需要在设置ContentView之前获取到Binding类的实例,以便于我们在之后的代码中使用,具体如下:

class ViewBindingActivity : AppCompatActivity() {

    private lateinit var mBinding: ActivityViewBindingBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
//        setContentView(R.layout.activity_view_binding)
        //视图绑定,方法1
        mBinding = ActivityViewBindingBinding.inflate(layoutInflater)
        //方法2
//        mBinding = ActivityViewBindingBinding.bind(LayoutInflater.from(this).inflate(R.layout.activity_view_binding, null))
        //使用root对象设置contentView
        setContentView(mBinding.root)
    }
}

如此一来,我们就完成了Activity的视图绑定了。我们可以看到,实际上没有什么区别,只是在进行setContentView之前,我们通过 inflate 或者 bind 方法获取到了一个Binding类的实例而已,然后使用其root对象来setContentView。

Fragment中使用ViewBinding

同样的,在Fragment中使用ViewBinding,我们只需要在onCreateView方法中返回view之前,获取到Binding类的实例即可,如下所示:

private lateinit var mBinding: FragmentTestBinding
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        //一般写法
//        return inflater.inflate(R.layout.fragment_test, container, false)
        
        //viewBinding写法
        mBinding = FragmentTestBinding.inflate(inflater)
        return mBinding.root
    }
自定义View中使用ViewBinding

对于组合控件的自定义View,即将xml布局封装成一个View时,我们也可以使用ViewBinding,事实上ViewBinding其实与组合控件的自定义View非常相似,就是将每个布局文件封装成了一个Binding对象,其中持有所有子view的引用。其在自定义View中的使用如下:

class MineItemView(context: Context) : ConstraintLayout(context) {

    private var mBinding: ItemMineBinding
    init {
        val view = LayoutInflater.from(context).inflate(R.layout.item_mine, this)
        mBinding = ItemMineBinding.bind(view)
    }
}
自定义Dialog中使用ViewBinding

对于Dialog而言,和Activity一样,我们只需要在onCreate方法中获取Binding类实例即可,如下:

private lateinit var mBinding: DialogMyViewBinding
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    mBinding = DialogMyViewBinding.inflate(layoutInflater)
    //设置dialog布局
    //        setContentView(R.layout.dialog_my_view)
    setContentView(mBinding.root)
}
Adapter中使用ViewBinding

在使用recycleView时,其Adapter中,我们也可以使用ViewBinding,我们知道在RecycleView中每个item类型,都有个ViewHolder与之对应,而且我们需要重写onCreateViewHolder方法,创建每个item类型对应的ViewHolder。在一般情况下,我们都会传入一个View,此时我们就可以根据该View来创建Binding类了,如下:

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHodler {
        return MineViewHolder(LayoutInflater.from(context).inflate(R.layout.item_mine,parent,false))
}
....
class MineViewHolder(itemView: View) : BaseViewHodler(itemView) {
        //一般写法
//        var tvName : TextView = itemView.findViewById(R.id.tvName)
//        var ivAvatar : ImageView = itemView.findViewById(R.id.ivAvatar)
//        var tvAge : TextView = itemView.findViewById(R.id.tvAge)
//        var btTest : Button = itemView.findViewById(R.id.btTest)

        var mBinding = ItemMineBinding.bind(itemView)

 }

然后,我们就可以使用ViewHolder的mBinding来获取对应的view组件了。或者,我们其实也可以这么写,如下:

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHodler {
    val binding = ItemMineBinding.inflate(LayoutInflater.from(context), parent, false)
    return MineViewHolder(binding)
}
...
class MineViewHolder(binding: ItemMineBinding) : BaseViewHodler(binding.root){
    var tvName : TextView = binding.tvName
    var ivAvatar : ImageView = binding.ivAvatar
    var tvAge : TextView = binding.tvAge
    var btTest : Button = binding.btTest
}
总结

我们可以看到,实际上无论是在Activity、Fragment还是Dialog、自定义View或者Adapter中使用ViewBinding,其视图绑定方法实际上与之前没有变化,所不同的就是需要获取一个Binding实例供之后使用,之后的操作实际上本质都是一样的,只不过view对象变成了Binding实例的root对象。

使用Binding类来操作View

在之前的介绍中,我们提到过,在完成视图绑定后,Binding类中会自动生成xml布局中的View引用,并可以以其id的驼峰形式来获取对应的引用,比如我们有如下xml布局文件 activity_view_binding


<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".viewbinding.ViewBindingActivity">

    <TextView
        android:id="@+id/tv_binding"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="textview"
        android:textSize="20sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.808" />

    <Button
        android:id="@+id/bt_binding"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="button"
        android:layout_marginBottom="100dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toTopOf="@id/tv_binding"/>

androidx.constraintlayout.widget.ConstraintLayout>

此时,系统会生成ActivityViewBindingBinding,该类中存在一个TextView——tvBinding和一个Button——btBinding,我们可以通过这两个对象来操作布局文件中的tv_binding和bt_binding,如下:

class ViewBindingActivity : AppCompatActivity() {

    private lateinit var mBinding: ActivityViewBindingBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
//        setContentView(R.layout.activity_view_binding)
        //视图绑定
        mBinding = ActivityViewBindingBinding.inflate(layoutInflater)
        setContentView(mBinding.root)
        //获取view对象
        mBinding.tvBinding.text = "binding success"
        mBinding.btBinding.setOnClickListener {
            mBinding.tvBinding.text = "click button"
        }
    }
}

运行结果正常。

include和merge

ViewBinding的使用非常简单,相对而言需要值得注意的就是布局中存在include和merge的情况。对于include,当我们想要获取到include中的View时,我们只需要在include对应的对象下寻找即可,如存在以下布局include_test.xml


<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <TextView
        android:id="@+id/tv_include"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="include"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

androidx.constraintlayout.widget.ConstraintLayout>

在Activity的布局中,我们使用include标签引入该布局,如下:


<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".viewbinding.ViewBindingActivity">

    <include
        android:id="@+id/include_test"
        layout="@layout/include_test"/>

androidx.constraintlayout.widget.ConstraintLayout>

此时,我们想要引用include布局中的textView,我们可以使用mBinding.includeTest.tvInclude,如下:

mBinding.includeTest.tvInclude.text = "viewBinding include"

注意:此时include标签的根View不能有id

但是,当我们将include_test.xml的根视图改为merge时,这样是不可行的,此时运行会报错。那么这时,我们应该怎么办呢?因为每个xml布局文件都存在对应的Binding类,此时,我们只需要将include标签中的布局文件的Binding类绑定到根View下即可,如下:

val includeTestBinding = IncludeTestBinding.bind(mBinding.root)
includeTestBinding.tvInclude.text = "this is a test message"

注意:此时include标签不能有id,否则会报错

比较

目前,谷歌在AndroidStudio4.1及以上版本新创建的项目已默认移除kotlin-android-extensions插件,官方更推荐使用ViewBinding、findViewById和DataBinding。那么这些视图绑定的方法都有些什么区别和优势呢?

  • findViewById:存在因视图 ID 无效而引发Null 指针异常 的风险和类型转换的风险;在布局复杂时使用不便。
  • DataBinding:DataBinding和ViewBinding均会生成可用于直接引用视图的绑定类。但是,ViewBinding旨在处理更简单的用例,与DataBinding相比不需要处理注释,具有更快的编译速度;同时也不需要使用 layout 标签进行标注,更加方便。当然DataBinding支持数据双向绑定和布局表达式,这是ViewBinding没有的,二者可以相互补充。
  • ButterKnife:与ViewBinding相比,ButterKnife的问题在于其编译速度相对较慢,而且其存在着编译安全性问题。
  • Android Kotlin Extensions:使用快捷方便,但是与ViewBinding相比存在出错的风险,且在最新的AndroidStudio4.1及以上版本以被默认移除。

如下图所示:

Android开发学习笔记——ViewBinding_第1张图片

总结

ViewBinding的使用方法非常简单,实际上ViewBinding就可以看作是将每个xml布局文件都封装成了一个自定义的View,只不过这个自定义View只保存了其中的子控件的对象引用,而没有其它的功能。在之前的项目开发中,一直都是使用的kotlin-android-extensions,虽然这确实十分方便,但是不得不说,这种方法还是存在着一些bug,容易在项目中出现一些未知的问题,而且也已经被谷歌抛弃。相对而言ViewBinding虽然多了个视图绑定的操作,但是其更加稳定,还是值得的,以后在项目中尽量去使用ViewBinding吧。

你可能感兴趣的:(Android开发学习笔记,Android,android,移动开发)