使用DiffUtil更新RecyclerView列表的最优实现方式是什么?
问题根因
你写的DiffUtil调用流程本身没有问题,列表不更新的核心原因几乎都是DataDiffUtilCallBack的两个核心判断方法实现有误:
areItemsTheSame:用于判断两个条目是否为同一个列表项,一般用业务唯一ID(比如你Model里的id字段)比对,该方法返回true才会进入后续内容比对逻辑areContentsTheSame:用于判断同一个条目的展示内容是否完全一致,只有该方法返回false时DiffUtil才会触发对应条目更新。如果漏比对了和UI相关的字段(比如你用到的enabled状态字段),DiffUtil就会认为内容没有变化,不会触发更新。
最优实现方案
不需要单独维护单条更新的方法,统一走DiffUtil逻辑即可,既避免重复代码,也能保证所有更新场景的一致性。
1. 修正DiffUtil回调实现
标准DiffUtil回调写法示例:
class CoinDiffCallback( private val oldList: List<Model>, private val newList: List<Model> ) : DiffUtil.Callback() { override fun getOldListSize(): Int = oldList.size override fun getNewListSize(): Int = newList.size override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { // 用唯一ID判断是否为同一个条目 return oldList[oldItemPosition].id == newList[newItemPosition].id } override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { val oldItem = oldList[oldItemPosition] val newItem = newList[newItemPosition] // 所有和UI展示相关的字段都要加入比对,漏哪个字段就会出现对应字段更新不生效的问题 return oldItem.name == newItem.name && oldItem.count == newItem.count && oldItem.enabled == newItem.enabled } // 可选实现:如果只需要局部更新不需要重绘整个条目,用payload返回变化的内容,减少不必要的性能消耗 override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? { val oldItem = oldList[oldItemPosition] val newItem = newList[newItemPosition] val payload = mutableMapOf<String, Any>() if (oldItem.enabled != newItem.enabled) { payload["ENABLED_CHANGE"] = newItem.enabled } // 其他字段变化同理加入payload return payload.takeIf { it.isNotEmpty() } } }
如果用了payload做局部更新,需要重载三参数的onBindViewHolder处理增量更新:
override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: MutableList<Any>) { if (payloads.isNotEmpty()) { val payload = payloads[0] as Map<String, Any> payload["ENABLED_CHANGE"]?.let { enabled -> holder.enableSwitch.isChecked = enabled as Boolean } return } super.onBindViewHolder(holder, position, payloads) }
2. 封装统一更新方法
在Adapter里封装通用的更新逻辑,不管是全量列表刷新还是单条数据更新都走同一套流程:
// 通用全量更新方法 fun updateData(newList: List<Model>) { val diffCallback = CoinDiffCallback(itemList, newList) val diffResult = DiffUtil.calculateDiff(diffCallback) // 注意必须给itemList赋值新的列表对象,不能直接修改原列表的内容,否则DiffUtil比对时新旧列表指向同一个对象,无法识别变化 itemList = newList.toMutableList() diffResult.dispatchUpdatesTo(this) } // 单条更新场景直接构造新列表调用通用方法即可 fun updateSingleItem(updatedItem: Model) { val newList = itemList.map { if (it.id == updatedItem.id) updatedItem else it } updateData(newList) }
注意事项
- 不要直接修改
itemList内部的元素属性再调用DiffUtil,必须生成新的列表对象传入,否则新旧列表比对时找不到差异 - 如果列表数据量超过1000条,可以把
calculateDiff逻辑放到后台线程执行,避免阻塞主线程
内容的提问来源于stack exchange,提问作者Alireza Mahfouzian
相关产品推荐
相关产品推荐

