You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于ListAdapter+DiffUtil,如何局部更新RecyclerView指定位置数据?

解决RecyclerView局部Item数据更新(避免全Item刷新)——基于ListAdapter + DiffUtil

要解决全Item刷新导致的UI波动问题,核心是利用DiffUtil精准识别数据变化,仅更新Item中发生变化的UI组件,而非重新绑定整个Item。以下是具体实现步骤:

1. 自定义DiffUtil.ItemCallback

DiffUtil.ItemCallback负责判断新旧Item的一致性,并返回具体变化细节:

  • areItemsTheSame:用Item的唯一标识(如ID)判断是否为同一对象,避免将内容变化误判为新Item。
  • areContentsTheSame:比较Item的所有字段,仅当完全一致时返回true,否则触发更新。
  • getChangePayload:可选但关键的方法,返回具体的变化字段,让Adapter明确需要更新哪些UI。

示例代码:

data class Item(val id: Int, val title: String, val count: Int)

class ItemDiffCallback : DiffUtil.ItemCallback<Item>() {
    override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
        // 唯一ID判断是否为同一Item
        return oldItem.id == newItem.id
    }

    override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
        // 比较所有字段,完全一致才不触发更新
        return oldItem.title == newItem.title && oldItem.count == newItem.count
    }

    override fun getChangePayload(oldItem: Item, newItem: Item): Any? {
        val changes = mutableMapOf<String, Any>()
        if (oldItem.title != newItem.title) {
            changes["title"] = newItem.title
        }
        if (oldItem.count != newItem.count) {
            changes["count"] = newItem.count
        }
        // 无变化返回null,否则返回变化集合
        return if (changes.isEmpty()) null else changes
    }
}

2. 实现支持局部更新的ListAdapter

继承ListAdapter,并重写onBindViewHolder的重载方法,通过payload实现局部更新:

  • 当payload为空时,执行全量数据绑定;
  • 当payload不为空时,仅更新对应变化的UI组件。

示例代码:

class ItemAdapter : ListAdapter<Item, ItemAdapter.ItemViewHolder>(ItemDiffCallback()) {

    inner class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        private val tvTitle: TextView = itemView.findViewById(R.id.tv_title)
        private val tvCount: TextView = itemView.findViewById(R.id.tv_count)

        // 全量绑定数据(首次加载或全Item更新时调用)
        fun bindFullData(item: Item) {
            tvTitle.text = item.title
            tvCount.text = item.count.toString()
        }

        // 局部更新UI(仅更新变化的字段)
        fun updatePartial(changes: Map<String, Any>) {
            changes["title"]?.let { tvTitle.text = it as String }
            changes["count"]?.let { tvCount.text = it.toString() }
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_layout, parent, false)
        return ItemViewHolder(view)
    }

    override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
        holder.bindFullData(getItem(position))
    }

    override fun onBindViewHolder(holder: ItemViewHolder, position: Int, payloads: MutableList<Any>) {
        if (payloads.isEmpty()) {
            // 无变化时调用全量绑定
            super.onBindViewHolder(holder, position, payloads)
        } else {
            // 有变化时执行局部更新
            val changes = payloads[0] as Map<String, Any>
            holder.updatePartial(changes)
        }
    }
}

3. 更新数据的正确方式

更新Item数据时,必须创建新的列表实例(不能直接修改原列表),然后调用submitList:

// 示例:更新index位置Item的count值
val currentList = adapter.currentList.toMutableList()
val updatedItem = currentList[index].copy(count = currentList[index].count + 1)
currentList[index] = updatedItem
adapter.submitList(currentList)

关键注意事项

  • 禁止手动调用notifyItemChanged,否则会绕过DiffUtil逻辑,导致全Item刷新;
  • areItemsTheSame必须使用唯一标识,不能依赖内容字段,否则会触发Item的移除/添加动画(而非更新动画);
  • getChangePayload返回的变化数据要精准,避免不必要的UI更新;
  • submitList必须传入新的列表对象,ListAdapter通过对比新旧列表的引用触发DiffUtil计算。

内容的提问来源于stack exchange,提问作者Anas Mirza

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 23:40:23