使用DiffUtil更新列表少量指定项是否必须新建对比列表
核心结论
不需要强制创建全量拷贝的全新列表实例,但有一条不能碰的红线:绝对不能直接修改Adapter当前已持有的列表的内部元素,且提交给DiffUtil比对的新旧两个列表都必须是不可变状态(提交后不能再修改元素内容、调整顺序),这是DiffUtil能正常计算差异、不出现UI刷新错乱的核心前提。
现有实现的问题
你贴的实现里每次更新都通过map遍历全量列表做转换,哪怕100个项里只需要改2、3个动态项,也要把剩下90多个不需要改动的非动态项全部遍历一遍,生成新的列表引用。当列表长度更大、动态项更新频率高的时候,这部分无意义的遍历和对象创建会带来不必要的性能开销。
DiffUtil本身的比对逻辑并不要求你把所有元素都重新生成一遍,它只会对比新旧列表对应位置的元素:如果元素的唯一标识(itemId/你在areItemsTheSame里的判断逻辑)一致,且内容比对(areContentsTheSame)一致,就会直接跳过这个项的刷新处理。
更优的低开销更新方案
你可以根据自己的业务场景选下面几种方案,都不需要做全量列表拷贝:
- 精准局部刷新(性能最高,零Diff计算开销)
如果你能明确拿到每个要更新的动态项在列表中的位置,完全不需要走submitList触发全量Diff计算,直接遍历待更新的项,找到对应position后调用adapter.notifyItemChanged(position, payload)即可。
配合payload参数可以实现定向绑定,只会刷新对应项里变化的控件,连其他非动态项的onBindViewHolder都不会触发,性能是所有方案里最好的。 - 最小修改构建新列表(适配必须走submitList的场景)
如果你需要统一走ListAdapter的submitList流程,不需要全量map转换,只需要基于原列表做最小范围的修改即可:
这种写法下,不需要改动的非动态项会直接保留原引用,DiffUtil比对时发现这些项的引用、内容都没有变化,会直接跳过比对,开销比全量map低很多。fun replaceDynamicItems(dynamicItems: List<DynamicItem>) { // 仅基于当前列表生成一个可变副本,不需要遍历转换所有元素 val listToSubmit = currentList.toMutableList() dynamicItems.forEach { updatedItem -> // 找到当前动态项对应的位置,匹配逻辑可以替换成你自己的业务规则(比如按ID匹配) val targetIndex = listToSubmit.indexOfFirst { it is DynamicItem && it.uniqueId == updatedItem.uniqueId } if (targetIndex != -1) { // 只替换需要更新的项,其余元素全部复用原引用 listToSubmit[targetIndex] = updatedItem } } adapter.submitList(listToSubmit) } - 开启稳定ID进一步加速Diff计算
给Adapter开启setHasStableIds(true),并重写getItemId()返回每个列表项的唯一固定标识,DiffUtil在比对时会优先通过ID判断是否为同一个项,差异计算的速度会进一步提升。
避坑提醒
禁止直接修改Adapter当前持有的列表:比如拿到currentList之后直接修改对应位置的元素,既不提交新列表也不调用notify方法,这种操作会导致DiffUtil做差异比对时,拿到的旧列表已经被篡改,计算出来的差异结果完全错误,最终出现列表项错位、内容错乱的问题。也不要在修改元素后直接调用
notifyDataSetChanged(),这种方式会触发全列表重绘,性能最差。
内容的提问来源于stack exchange,提问作者sssrigo
相关产品推荐
相关产品推荐

