Preface: this article is based on BaseRecyclerViewAdapterHelper – 2.9.34 version carries on the analysis, this is my first technical article published, if write bad please include me hard to understand, thank you! The article is long, but not very complicated, easy to understand, if you have any questions in the comments section; If you see not bottom go to, can be directly the final summary and BaseRecyclerViewAdapterHelper ViewBinding final assembly code This article mainly want to readers in this paper, the two problems and solutions:
- BaseViewHolder based on BRVAH when customizingViewholder occur class conversion exception problems and ideas
- BRVAH and ViewBinding encapsulation make it easier to use controls in adapters
PS: BaseRecyclerViewAdapterHelper I BRVAH for short
1. Simple BRVAH combined with ViewBinding
The code for simple encapsulation is as follows. The core is to add a ViewBinding attribute to the ViewHolder: 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)
}
}
Copy the code
VBViewHolder.kt
class VBViewHolder<VB: ViewBinding>(val binding: VB, view: View): BaseViewHolder(view)
Copy the code
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("If we had never met."."Where would you be?"."Live every second."."Dying every second."."If we had never met."."Where would you be?"."Live every second."."Dying every second.")
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)}}Copy the code
This packaging looks no problem, write an interface to add a RecyclerView test can also normal display operation
2. Set up an emptyView and find a BRVAH error in Logcat
Add the following code:
mBinding.contentRv.apply {
layoutManager = LinearLayoutManager(this@MainActivity2)
adapter = InnerAdapter().also {
// Add an empty layout
it.setEmptyView(R.layout.item_item, this)}}Copy the code
Re-run the demo and it works fine, no crashes, but there is an error in Logcat:Meaning there is no custom in myVBViewHolder
Found a constructor with arguments of type View, indeed I customVBViewHolder
Constructor arguments View and ViewBinding for BRVAHReflection needs to find a constructor that has only a View argument, and you can see that this place istry-catch
If not, we catch the exception and return it to null. We see that this is where the error message was caught and printed, which fosters the crash that follows
3. Rewrite the RecyclerView onBindViewHolder method.
Rewrite onBindViewHolder in RecyclerView adapter class code as follows:
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)
}
}
Copy the code
When we call the parent class’s method and re-run the demo, we find that the application crashes. The crash message is as follows:Say that IBaseViewHolder
Converted toVBViewHolder
Failed, logically I am in the custom adapterBaseBindingAdapter
Override method inonCreateDefViewHolder
Returns my customVBViewHolder
, so logically this conversion failure should not happen. I wonder if I added an emptyView in step 2,I got rid of this and rewrote the app, and it worked fine without crashing
4. Analyze the cause of collapse with BRVAH
According to the previous analysis, the phenomenon of the problem is as follows:
The onBindViewHolder application crashed when emptyView was removed or the onBindViewHolder method was removed
Let’s analyze this phenomenon according to the source code:
- BRAVH did not call me to override the ViewHolder created for emptyView
onCreateDefViewHolder
The ViewHolder corresponding to emptyView is not customVBViewHolder
BRAVH provided itBaseViewHolder
You can see:
- If you add emptyView, footView, headerView, it will pass
createBaseViewHolder
The ViewHolder () method creates a ViewHolder- If the ViewHolder is created using the data source we passed in, our custom methods will be used
onCreateDefViewHolder
returnVBViewHolder
createBaseViewHolder()
Perform logical analysis
You can see:
- Class variable
z
throughgetInstancedGenerickClass
Method returns what we passedBaseQuickAdapter<T, K entends BaseViewHolder>
The class object of VBViewHolder passed in (when we inherit from BaseQuickAdapter we need to pass in a generic type whose upper bound is BaseViewHolder, which is our custom VBViewHolder)- through
createGenericKInstance()
Method to try to create a ViewHolder object, and then examine the execution flow of the method
createGenericKInstance()
Perform logical analysis
You can see:
- This method, which we briefly examined earlier, takes a constructor with a View argument via reflection, however
VBViewHolder
Is not provided in logcat, so an error occurs that was not found in the previous logcat method- And then eventually the result is returned to NULL, and we go back to the previous method
createBaseViewHolder()
In the execution logic of
The last code returned by createBaseViewHolder() will be pasted again:
Through our work oncreateGenericKInstance()
Analysis of the variablesk
The value of k will eventually be assigned to null, the logic in the red box is that when the variable k is null we will create oneBaseViewHolder
Object strongly casts to the type of the generic K,Here lies the root of the problem
First, we know:
Generics are erased at compile time. If there is no upper bound, the erased type is Object, if there is an upper bound, the erased type is upper bound, and the erased type is BaseViewHolder, So the type of BaseViewHolder that you force is going to be BaseViewHolder, which is this right here which is the Holder type that emptyView corresponds to
onBindViewHolder()
Methods to analyze
See how the parent RecyclerView method looks like:
And then when we rewrite it in the RecyclerView adapteronBindViewHolder
Here’s what it looks like:
You can see:
- RecyclerView original
onBindViewHolder
The first parameter passed to the method is of type VH, which is passed through the following
So this VH type is just after the generic erasureBaseViewHolder
Type, so when we call nativeonBindViewHolder
EmptyView (holder)
- If we override it in a custom adapter
onBindViewHolder
Method,The first argument type is VBViewHolder, but the emptyView we passed is BaseViewHolder, so the application crashes
5. Solutions to problems
From our analysis above, we can see that the key point that went wrong was that the ViewHolder created for emptyView was BaseViewHolder rather than VBViewHolder. CreateGenericKInstance () failed because our custom ViewHolder does not have a constructor that contains only View as a type argument. So we can solve this problem by creating a constructor in our custom ViewHolder that contains only the View parameter type
The code is as follows: vbViewholder.kt
class VBViewHolder<VB: ViewBinding> @JvmOverloads constructor(view: View, val binding: VB? = null): BaseViewHolder(view)
Copy the code
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)
}
}
Copy the code
6. Summary
If we define a ViewHolder class based on BRVAH's BaseViewHolder that does not provide a constructor that contains only one View constructor parameter, then when we set emptyView, footView, headerView, BRVAH creates a BaseViewHolder for these views, and a cast error occurs when we override onBindViewholder() to indicate that the Viewholder type is our custom Holder type
So when we customize a ViewHolder class for BRVAH's BaseViewHolder, the class must provide a constructor that contains only one View constructor parameter