前言:本文是基于
BaseRecyclerViewAdapterHelper-2.9.34
版本进行分析的,这是本人发表的第一篇技术文章,如果写的不好难以理解还请多多包含,谢谢!本片文章篇幅比较长,但是都不是很复杂的、很容易理解,如果大家有疑问在评论区留言;如果感觉看不下去了,可以直接最后的总结和BaseRecyclerViewAdapterHelper与ViewBinding最终的封装代码
本文主要是想要向读者阐述两个问题以及解决方式:
- 基于BRVAH的BaseViewHolder自定义ViewHolder的时候出现的类转换异常的问题以及思路方法
- 基于BRVAH与ViewBinding封装,让大家使用在适配器中使用控件更加方便
PS:BaseRecyclerViewAdapterHelper我就简称为BRVAH
简单封装的代码如下,核心是往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测试下也能正常显示运行
添加代码如下:
mBinding.contentRv.apply {
layoutManager = LinearLayoutManager(this@MainActivity2)
adapter = InnerAdapter().also {
//添加一个空布局
it.setEmptyView(R.layout.item_item, this)
}
}
重新运行demo,运行起来正常没啥问题,没发生崩溃,但是却在Logcat中发现一个错误:
意思就是没有在我自定义的VBViewHolder
中找到一个参数为View类型的构造方法,确实我自定义的VBViewHolder
中的构造方法中参数View和ViewBinding,看了下BRVAH中源码报错的位置:
反射需要找下只有View一个参数的构造器,可以看到这个地方被try-catch
了,找不到的话就捕捉下异常然后返回为null值,我们看到这个错误信息就是这个地方捕捉到打印的,这就为接下来的崩溃买下了伏笔
在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,发现应用崩溃掉了,崩溃的信息如下:
说是我BaseViewHolder
转换成VBViewHolder
失败了,按道理来我在自定义的适配器BaseBindingAdapter
中重写了方法onCreateDefViewHolder
返回了我自定义的VBViewHolder
,所以讲道理这个转换失败不应该发生。想了下是不是我第2步加了个emptyView导致的崩溃,去掉了这个然后重写跑了下应用,应用正常运行没有发生崩溃
根据前面的分析,问题的现象如下:
加了emptyView并重写了
onBindViewHolder
方法应用发生了崩溃,去掉了emptyView或者去掉onBindViewHolder
方法的重写崩溃就没有了,运行正常
接下来就让我们根据源码进行分析下这种现象:
onCreateDefViewHolder
,即emptyView对应的ViewHolder不是自定义的VBViewHolder
,而是BRAVH提供的BaseViewHolder
- 如果添加了emptyView、footView、headerView,将会通过
createBaseViewHolder
方法创建ViewHolder- 如果是通过我们传入的数据源创建的ViewHolder才会走我们自定义的方法
onCreateDefViewHolder
返回VBViewHolder
createBaseViewHolder()
执行逻辑分析
- Class类型的变量
z
通过getInstancedGenerickClass
方法返回的是我们通过BaseQuickAdapter
传入的VBViewHolder的class对象(当我们继承BaseQuickAdapter需要传入一个上界为BaseViewHolder一个泛型类型,此处传入的就是我们自定义的VBViewHolder)- 通过
createGenericKInstance()
方法去尝试创建一个ViewHolder对象,接下来分析下这个方法的执行流程
createGenericKInstance()
执行逻辑分析
- 该方法我们之前简单分析过,会通过反射获取一个带有View参数的构造器,然而
VBViewHolder
中并没有提供,所以就会发生之前logcat中方法未找到的错误- 然后最终这个结果返回为null,我们回到上一个方法
createBaseViewHolder()
的执行逻辑中
createBaseViewHolder()
方法中最后返回的代码我在粘贴一次:
通过我们对 createGenericKInstance()
的分析可知,变量k
的值最终会被赋值为null,红框中的逻辑那就是当变量k为null的时候我们将创建一个BaseViewHolder
对象强转成泛型K的类型,问题关键的根源就在这里
,首先我们晓得:
泛型会在编译期间进行擦除,如果没有上界,那么类型擦除后的类型就是Object,如果有上界的那么擦除后的类型就是上界类型,最终擦除后的类型就是
BaseViewHolder
,所以此处BaseViewHolder
对象强转后的类型就是BaseViewHolder
类型,即这个也就是emptyView对应的Holder类型
onBindViewHolder()
方法分析onBindViewHolder
方法的时候,再看下是什么样子:通过我们上面的分析可以看出,问题出错的关键点就是为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)
}
}
如果我们基于BRVAH的BaseViewHolder自定义一个ViewHolder类的时候,这个类中不提供个只包含一个View类型的构造参数的构造方法,那么当我们的设置emptyView、footView、headerView的时候,BRVAH会为这些View创建一个BaseViewHolder,而当我们重写onBindViewholder()等在参数中指明了Viewholder类型是我们自定义的Holder类型的时候就会出现类型转换错误
所以当我们基于BRVAH的BaseViewHolder自定义一个ViewHolder类的时候,这个类中一定要提供个只包含一个View类型的构造参数的构造方法