基于BaseRecyclerViewAdapterHelper与ViewBinding爬坑封装之旅

·  阅读 1060
基于BaseRecyclerViewAdapterHelper与ViewBinding爬坑封装之旅

前言:本文是基于BaseRecyclerViewAdapterHelper-2.9.34版本进行分析的,这是本人发表的第一篇技术文章,如果写的不好难以理解还请多多包含,谢谢!本片文章篇幅比较长,但是都不是很复杂的、很容易理解,如果大家有疑问在评论区留言;如果感觉看不下去了,可以直接最后的总结和BaseRecyclerViewAdapterHelper与ViewBinding最终的封装代码
本文主要是想要向读者阐述两个问题以及解决方式:

  • 基于BRVAH的BaseViewHolder自定义ViewHolder的时候出现的类转换异常的问题以及思路方法
  • 基于BRVAH与ViewBinding封装,让大家使用在适配器中使用控件更加方便

PS:BaseRecyclerViewAdapterHelper我就简称为BRVAH


1. 简单的BRVAH与ViewBinding的结合

简单封装的代码如下,核心是往ViewHolder添加个ViewBinding的属性方便调用: BaseBindingAdapter.kt

abstract class BaseBindingAdapter<VB: ViewBinding, T>(data: List<T>? = null):
        BaseQuickAdapter<T, VBViewHolder<VB>>(0, data) {

    override fun convert(holder: VBViewHolder<VB>, item: T) {
        convertPlus(holder.binding, item)
    }

    abstract fun convertPlus(binding: VB, item: T)

    abstract fun createViewBinding(inflater: LayoutInflater, parent: ViewGroup): VB

    override fun onCreateDefViewHolder(parent: ViewGroup, viewType: Int): VBViewHolder<VB> {
        val binding = createViewBinding(LayoutInflater.from(parent.context), parent)
        return VBViewHolder(binding, binding.root)
    }
}
复制代码

VBViewHolder.kt

class VBViewHolder<VB: ViewBinding>(val binding: VB, view: View): BaseViewHolder(view)
复制代码

MainActivity2.kt

class MainActivity2 : AppCompatActivity() {

    private val mBinding by lazy {
        ActivityMain2Binding.inflate(layoutInflater)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(mBinding.root)
        val list = listOf("如果我们不曾相遇", "你会是在哪里", "每秒都活着", "每秒都死去",
                "如果我们不曾相遇", "你会是在哪里", "每秒都活着", "每秒都死去")
        mBinding.contentRv.apply {
            layoutManager = LinearLayoutManager(this@MainActivity2)
            adapter = InnerAdapter()
        }
    }

    class InnerAdapter(list: List<String> = ArrayList()): BaseBindingAdapter<ItemItemBinding, String>(list) {
        
        override fun convertPlus(binding: ItemItemBinding, item: String) {
            binding.desTv.text = item
        }

        override fun createViewBinding(inflater: LayoutInflater, parent: ViewGroup) =
            ItemItemBinding.inflate(inflater, parent, false)
    }
}
复制代码

这样封装看起来没啥毛病,写个界面添加个RecyclerView测试下也能正常显示运行

2. 设置个emptyView,Logcat中发现一个BRVAH中异常错误(伏笔)

添加代码如下:

mBinding.contentRv.apply {
            layoutManager = LinearLayoutManager(this@MainActivity2)
            adapter = InnerAdapter().also {
            //添加一个空布局
                it.setEmptyView(R.layout.item_item, this)
            }
        }
复制代码

重新运行demo,运行起来正常没啥问题,没发生崩溃,但是却在Logcat中发现一个错误: image.png 意思就是没有在我自定义的VBViewHolder中找到一个参数为View类型的构造方法,确实我自定义的VBViewHolder中的构造方法中参数View和ViewBinding,看了下BRVAH中源码报错的位置: image.png 反射需要找下只有View一个参数的构造器,可以看到这个地方被try-catch了,找不到的话就捕捉下异常然后返回为null值,我们看到这个错误信息就是这个地方捕捉到打印的,这就为接下来的崩溃买下了伏笔

3. 重写下RecyclerView的onBindViewHolder方法,崩了?

在RecyclerView的适配器类代码中重写下onBindViewHolder方法,代码如下:

class InnerAdapter(list: List<String> = ArrayList()): BaseBindingAdapter<ItemItemBinding, String>(list) {

        override fun onBindViewHolder(holder: VBViewHolder<ItemItemBinding>, position: Int, payloads: MutableList<Any>) {
            super.onBindViewHolder(holder, position, payloads)
        }
    }
复制代码

可以看到这个重写的方法中,我们什么都没有做,只是进行了下重写,然后调用了父类的方法,重新运行下demo,发现应用崩溃掉了,崩溃的信息如下: image.png 说是我BaseViewHolder转换成VBViewHolder失败了,按道理来我在自定义的适配器BaseBindingAdapter中重写了方法onCreateDefViewHolder返回了我自定义的VBViewHolder,所以讲道理这个转换失败不应该发生。想了下是不是我第2步加了个emptyView导致的崩溃,去掉了这个然后重写跑了下应用,应用正常运行没有发生崩溃

4. 结合BRVAH分析崩溃原因

根据前面的分析,问题的现象如下:

加了emptyView并重写了onBindViewHolder方法应用发生了崩溃,去掉了emptyView或者去掉onBindViewHolder方法的重写崩溃就没有了,运行正常

接下来就让我们根据源码进行分析下这种现象:

  • BRAVH为emptyView创建的ViewHolder时并没有调用我重写onCreateDefViewHolder,即emptyView对应的ViewHolder不是自定义的VBViewHolder,而是BRAVH提供的BaseViewHolder

image.png 可以看到:

  • 如果添加了emptyView、footView、headerView,将会通过createBaseViewHolder方法创建ViewHolder
  • 如果是通过我们传入的数据源创建的ViewHolder才会走我们自定义的方法onCreateDefViewHolder返回VBViewHolder
  • createBaseViewHolder()执行逻辑分析

image.png
可以看到:

  • Class类型的变量z通过getInstancedGenerickClass方法返回的是我们通过BaseQuickAdapter<T, K entends BaseViewHolder>传入的VBViewHolder的class对象(当我们继承BaseQuickAdapter需要传入一个上界为BaseViewHolder一个泛型类型,此处传入的就是我们自定义的VBViewHolder)
  • 通过createGenericKInstance()方法去尝试创建一个ViewHolder对象,接下来分析下这个方法的执行流程
  • createGenericKInstance()执行逻辑分析

image.png 可以看到:

  • 该方法我们之前简单分析过,会通过反射获取一个带有View参数的构造器,然而VBViewHolder中并没有提供,所以就会发生之前logcat中方法未找到的错误
  • 然后最终这个结果返回为null,我们回到上一个方法createBaseViewHolder()的执行逻辑中

createBaseViewHolder()方法中最后返回的代码我在粘贴一次:

image.png
通过我们对 createGenericKInstance()的分析可知,变量k的值最终会被赋值为null,红框中的逻辑那就是当变量k为null的时候我们将创建一个BaseViewHolder对象强转成泛型K的类型,问题关键的根源就在这里,首先我们晓得:

泛型会在编译期间进行擦除,如果没有上界,那么类型擦除后的类型就是Object,如果有上界的那么擦除后的类型就是上界类型,最终擦除后的类型就是BaseViewHolder,所以此处BaseViewHolder对象强转后的类型就是BaseViewHolder类型,即这个也就是emptyView对应的Holder类型

  • onBindViewHolder()方法分析

我们先看下父类RecyclerView的这个原生的方法长什么样子: image.png
然后当我们RecyclerView的适配器中重写了onBindViewHolder方法的时候,再看下是什么样子: image.png
可以看到:

  • RecyclerView原始的onBindViewHolder方法传入的第一个参数的类型就是VH类型,而这个类型就是通过下面

image.png image.png
所以这个VH类型就是泛型擦除之后的BaseViewHolder类型,所以当我们调用原生的onBindViewHolder传入emptyView对应的holder是正常的不会报错

  • 而如果我们在自定义的适配器中重写onBindViewHolder方法时,指定了第一个参数类型就是VBViewHolder,而我们传入的emptyView对应的类型却是BaseViewHolder类型的,所以直接发生了类型转换异常,应用程序崩溃

5. 问题的解决方法

通过我们上面的分析可以看出,问题出错的关键点就是为emptyView创建的ViewHolder类型是BaseViewHolder类型而不是VBViewHolder类型,而为什么没有创建出VBViewHolder的类型就是因为在方法createGenericKInstance()创建失败了,失败的原因就是我们自定义的ViewHolder中没有只包含View一个类型参数的构造方法,所以我们在我们自定义的ViewHolder中创建一个只包含View参数类型的构造方法即可解决

代码如下: VBViewHolder.kt

class VBViewHolder<VB: ViewBinding> @JvmOverloads constructor(view: View, val binding: VB? = null): BaseViewHolder(view)
复制代码

BaseBindingAdapter.kt

abstract class BaseBindingAdapter<VB: ViewBinding, T>(data: List<T>? = null):
        BaseQuickAdapter<T, VBViewHolder<VB>>(0, data) {

    override fun convert(holder: VBViewHolder<VB>, item: T) {
        convertPlus(holder.binding!!, item)
    }

    abstract fun convertPlus(binding: VB, item: T)

    abstract fun createViewBinding(inflater: LayoutInflater, parent: ViewGroup): VB

    override fun onCreateDefViewHolder(parent: ViewGroup, viewType: Int): VBViewHolder<VB> {
        val binding = createViewBinding(LayoutInflater.from(parent.context), parent)
        return VBViewHolder(binding.root, binding)
    }
}
复制代码

6. 总结

如果我们基于BRVAH的BaseViewHolder自定义一个ViewHolder类的时候,这个类中不提供个只包含一个View类型的构造参数的构造方法,那么当我们的设置emptyView、footView、headerView的时候,BRVAH会为这些View创建一个BaseViewHolder,而当我们重写onBindViewholder()等在参数中指明了Viewholder类型是我们自定义的Holder类型的时候就会出现类型转换错误

所以当我们BRVAH的BaseViewHolder自定义一个ViewHolder类的时候,这个类中一定要提供个只包含一个View类型的构造参数的构造方法

分类:
Android
标签:
分类:
Android
标签: