基于ViewBinding的通用RecyclerView Adapter实现问题求解
通用RecyclerView Adapter实现的核心问题与解决方案
问题背景
尝试实现一个通用RecyclerView Adapter,当前代码如下:
class RVAdapter <T: Any, VB: ViewBinding>( private var dataSet: List<T>, private val bindingInterface: RVBinding<T, VB> ) : ListAdapter<T, RVAdapter<T, VB>.RVHolder>(DiffCallback<T>()) { inner class RVHolder(private val binding: ViewBinding): RecyclerView.ViewHolder(binding.root) { fun<T: Any, VB: ViewBinding> bind(item: T, bindingInterface: RVBinding<T, VB>) { bindingInterface.bind(item, (binding as VB)) // 未检查的类型转换 } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RVHolder { val binding = (VB as ViewBinding).inflate() // 问题1:如何通过泛型VB填充布局? return RVHolder(binding) } override fun onBindViewHolder(holder: RVHolder, position: Int) { holder.bind(getItem(position), bindingInterface) } override fun getItemCount() = dataSet.size } private class DiffCallback<T: Any>: DiffUtil.ItemCallback<T>() { // 问题2:如何通用实现这两个方法? override fun areItemsTheSame(oldItem: T, newItem: T): Boolean { TODO("Not yet implemented") } override fun areContentsTheSame(oldItem: T, newItem: T): Boolean { return oldItem == newItem } } interface RVBinding<T: Any, VB: ViewBinding> { fun bind(item: T, binding: VB) }
核心问题
- 如何在
onCreateViewHolder()中实例化泛型VB类型的ViewBinding?常规的SpecificBinding.inflate()方式在泛型场景下无法直接使用; - 如何以通用方式实现
DiffUtil.ItemCallback的areItemsTheSame和areContentsTheSame方法?直接比较toString或==(依赖equals实现)的方式不具备通用性。
解决方案
1. 泛型ViewBinding的实例化解决
由于Kotlin泛型的类型擦除特性,无法直接通过VB类调用inflate方法,推荐将布局创建逻辑通过构造参数传入,同时优化ViewHolder消除类型转换警告:
class RVAdapter <T: Any, VB: ViewBinding>( private val bindingFactory: (LayoutInflater, ViewGroup, Boolean) -> VB, private val bindItem: (T, VB) -> Unit, diffCallback: DiffUtil.ItemCallback<T> ) : ListAdapter<T, RVAdapter<T, VB>.RVHolder>(diffCallback) { inner class RVHolder(private val binding: VB): RecyclerView.ViewHolder(binding.root) { fun bind(item: T) { bindItem(item, binding) } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RVHolder { val inflater = LayoutInflater.from(parent.context) val binding = bindingFactory(inflater, parent, false) return RVHolder(binding) } override fun onBindViewHolder(holder: RVHolder, position: Int) { holder.bind(getItem(position)) } }
使用时传入Binding的创建逻辑:
val adapter = RVAdapter( { inflater, parent, attach -> ItemBinding.inflate(inflater, parent, attach) }, { item, binding -> binding.tvName.text = item.name }, // 后面会补充DiffCallback的实现 )
2. 通用DiffUtil.ItemCallback实现
提供两种通用方案,按需选择:
方案一:定义接口约束数据类
让所有数据类实现统一接口,提供唯一标识和内容比较能力:
interface Diffable { fun getUniqueId(): Any fun contentEquals(other: Diffable): Boolean } class DiffCallback<T: Diffable>: DiffUtil.ItemCallback<T>() { override fun areItemsTheSame(oldItem: T, newItem: T): Boolean { return oldItem.getUniqueId() == newItem.getUniqueId() } override fun areContentsTheSame(oldItem: T, newItem: T): Boolean { return oldItem.contentEquals(newItem) } }
数据类实现示例:
data class Item(val id: String, val name: String): Diffable { override fun getUniqueId(): Any = id override fun contentEquals(other: Diffable): Boolean { return other is Item && this == other } }
方案二:构造时传入判断逻辑
无需修改数据类,直接在Adapter构造时传入两个lambda处理Diff判断:
// 创建Adapter时直接传入Diff逻辑 val adapter = RVAdapter( { inflater, parent, attach -> ItemBinding.inflate(inflater, parent, attach) }, { item, binding -> binding.tvName.text = item.name }, object : DiffUtil.ItemCallback<Item>() { override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean { return oldItem.id == newItem.id } override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean { return oldItem == newItem } } )
其他优化建议
- 移除
getItemCount重写:ListAdapter已通过currentList实现该方法,无需重复编写; - 使用
submitList更新数据:ListAdapter依赖此方法感知数据变化,不要直接修改dataSet; - 简化绑定逻辑:将
RVBinding接口替换为lambda,进一步精简代码(如上述示例所示)。
内容的提问来源于stack exchange,提问作者Sorry
相关产品推荐
相关产品推荐

