MultiType 是一个非常简洁灵活好用的RecyclerView#Adapter的第三方封装库,它能轻松实现多类型列表布局,且只需要关注数据类型、列表布局及数据更新逻辑,无需写其他的样板代码,真的很香。
GitHub地址: MultiType
GitHub给出的已实现的效果图如下:
基本使用
- 创建类型布局对应的ItemBuilder
class OnlyTitleItemBuilder(private val clickBehavior: ((item: String, view: View) -> Unit)?): ItemViewBinder<String, BindingViewHolder<ItemOnlyTitleBinding>>() {
override fun onCreateViewHolder(
inflater: LayoutInflater,
parent: ViewGroup
): BindingViewHolder<ItemOnlyTitleBinding> {
return BindingViewHolder(
ItemOnlyTitleBinding.inflate(inflater, parent, false)
)
}
override fun onBindViewHolder(holder: BindingViewHolder<ItemOnlyTitleBinding>, item: String) {
holder.itemView.setOnClickListener {
clickBehavior?.invoke(item, it)
}
holder.binding.tvTitle.text = item
}
}
class ImageTextItemBuilder(private val clickBehavior: ((item: ImageTextData, view: View) -> Unit)?)
: ItemViewBinder<ImageTextData, BindingViewHolder<ItemImageTextBinding>>() {
override fun onCreateViewHolder(
inflater: LayoutInflater,
parent: ViewGroup
): BindingViewHolder<ItemImageTextBinding> {
return BindingViewHolder(
ItemImageTextBinding.inflate(inflater, parent, false)
)
}
override fun onBindViewHolder(holder: BindingViewHolder<ItemImageTextBinding>, item: ImageTextData) {
holder.itemView.setOnClickListener {
clickBehavior?.invoke(item, it)
}
holder.binding.ivImage.setImageURI(item.imageUrl)
holder.binding.tvTitle.text = item.title
}
}
- 将ItemBuilder注册到MultiTypeAdapter中
private val multiTypeAdapter by lazy {
MultiTypeAdapter()
}
private fun setupMultiTypeList() {
multiTypeAdapter.register(OnlyTitleItemBuilder() { item, view ->
})
multiTypeAdapter.register(ImageTextItemBuilder() { item, view ->
})
val linearLayoutManager = LinearLayoutManager(this,LinearLayoutManager.VERTICAL, false)
binding.multitypeList.layoutManager = linearLayoutManager
binding.multitypeList.adapter = multiTypeAdapter
binding.multitypeList.itemAnimator = null
}
- 更新数据到MultiTypeAdapter
private val items = mutableListOf<Any>()
private fun loadData() {
items.add("Item Title 1")
items.add("Item Title 2")
items.add(ImageTextData(
TEST_IMAGE_YUGI_URL,
"Item Title 3"
))
items.add(ImageTextData(
TEST_IMAGE_JUNNING_URL,
"Item Title 4"
))
multiTypeAdapter.items = items
multiTypeAdapter.notifyDataSetChanged()
}
上面demo代码运行效果如下:
实现原理分析
-
主要思路
MultiType库的核心类是MultiTypeAdapter。
自定义RecyclerView.Adapter的子类,通过register方法关联viewType值与delegate,
重写 getItemViewType 方法获取自定义viewType,这样在onCreateViewHolder,onBindViewHolder方法中,就可以根据viewType去调用对应ItemViewDelegate的onCreateViewHolder,onBindViewHolder方法,以完成viewHolder的创建及刷新。 -
源码实现
a. 注册viewTypeinline fun <reified T : Any> register(binder: ItemViewBinder<T, *>) { register(binder as ItemViewDelegate<T, *>) } fun <T> register(clazz: Class<T>, delegate: ItemViewDelegate<T, *>) { unregisterAllTypesIfNeeded(clazz) //注释1:将clazz与delegate封装为Type,并保存到List中 register(Type(clazz, delegate, DefaultLinker())) } internal fun <T> register(type: Type<T>) { //注释2:types是list类型,这里相当于list#add方法 types.register(type) type.delegate._adapter = this } data class Type<T>( val clazz: Class<out T>, val delegate: ItemViewDelegate<T, *>, val linker: Linker<T> )
register方法,最后会将数据类型clazz与ItemViewDelegate封装为Type类型并保存到列表(types)中,如注释1和注释2所示。
b. 重写getItemViewType
override fun getItemViewType(position: Int): Int { return indexInTypesOf(position, items[position]) } @Throws(DelegateNotFoundException::class) internal fun indexInTypesOf(position: Int, item: Any): Int { val index = types.firstIndexOf(item.javaClass) if (index != -1) { val linker = types.getType<Any>(index).linker //注释3:register方法默认设置的是DefaultLinker,linker#index方法会返回0 //所以实际这里返回的是item对应类型的下标 return index + linker.index(position, item) } throw DelegateNotFoundException(item.javaClass) }
核心代码是
val index = types.firstIndexOf(item.javaClass)
, getItemViewTyp方法实际获取的是item类型对应的列表下标。
本质上就是,在register方法注册delegate时,delegate注册时对应的index,就是它的viewType。
后续的onCreateViewHolder、onBindViewHolder方法,都需要根据viewType找到对应的delegate,然后调用delegate对应的方法。c. 重写onCreateViewHolder方法
//RecyclerView内部调用onCreateViewHolder方法前会先调用getItemViewType方法 override fun onCreateViewHolder(parent: ViewGroup, indexViewType: Int): ViewHolder { return types.getType(indexViewType) //根据viewType获取自定义的ItemViewDelegate .delegate //调用自定义ItemViewDelegate的onCreateViewHolder方法 .onCreateViewHolder(parent.context, parent) }
在onCreateViewHolder方法中,通过在getItemViewType方法中获取的indexViewType,在列表(types)中取出对应的delegate, 调用delegate#onCreateViewHolder方法,走到自定义的viewHolder创建逻辑。
d. 重写onBindViewHolder方法
override fun onBindViewHolder(holder: ViewHolder, position: Int) { onBindViewHolder(holder, position, emptyList()) } override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: List) { val item = items[position] getOutDelegateByViewHolder(holder).onBindViewHolder(holder, item, payloads) } private fun getOutDelegateByViewHolder(holder: ViewHolder): ItemViewDelegate<Any, ViewHolder> { //根据viewType查找自定义ItemViewDelegate @Suppress("UNCHECKED_CAST") return types.getType<Any>(holder.itemViewType) .delegate as ItemViewDelegate<Any, ViewHolder> }
ViewHolder#itemViewType属性是在RecyclerView#createViewHolder方法中设置的,属性值就是getItemViewType方法的返回值,具体参见RecycerView的源码,关键代码如下:
@Nullable ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) { ...... final int type = mAdapter.getItemViewType(offsetPosition); ...... holder = mAdapter.createViewHolder(RecyclerView.this, type); ...... } public final VH createViewHolder(@NonNull ViewGroup parent, int viewType) { try { TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG); final VH holder = onCreateViewHolder(parent, viewType); if (holder.itemView.getParent() != null) { throw new IllegalStateException("xxx"); } holder.mItemViewType = viewType;//设置viewType return holder; } finally { TraceCompat.endSection(); } }
所以在onBindViewHolder方法中,同样是通过getItemViewType获取的indexViewType, 然后在列表(types)中取出对应的delegate, 调用delegate#onBindViewHolder方法,走到自定义的绑定viewHolder数据的逻辑。
以上就是所有内容,有不对的地方,欢迎留言交流。