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

带筛选与Payload更新的RecyclerView Adapter同步问题咨询

基于Filter和AsyncListDiffer的RecyclerView Adapter状态同步问题

问题背景

需要实现支持Filter筛选、Payload局部更新的RecyclerView Adapter,因数据量较大,采用AsyncListDiffer做异步更新。

核心问题

  • 筛选状态下修改列表项的收藏状态(星号标记),清除筛选后该状态丢失
  • AsyncListDiffer异步更新时存在元素更新冲突

怀疑点

问题出在submitList和updateItem方法的逻辑中,原代码如下:

class Adapter(
    private val listener: AdapterListener
): RecyclerView.Adapter<RecyclerView.ViewHolder>(), Filterable {

    private val diffCallback = object : DiffUtil.ItemCallback<UiModel>() {

        override fun areItemsTheSame(oldItem: UiModel, newItem: UiModel): Boolean {
            return oldItem.id == newItem.id
        }

        override fun areContentsTheSame(oldItem: UiModel, newItem: UiModel): Boolean {
            return (
                oldItem.name == newItem.name &&
                oldItem.id == newItem.id &&
                oldItem.isFavorite == newItem.isFavorite
            )
        }
    }

    private var listDiffer = AsyncListDiffer(this, diffCallback)
    private val initialDataItems = mutableListOf<UiModel>()
    private val dataFilter = object : Filter() {

        override fun performFiltering(constraint: CharSequence?): FilterResults {
            val queryRawName = constraint?.toString()
            if (queryRawName.isNullOrEmpty()) return FilterResults().apply { values = initialDataItems }
            val queryName = queryRawName.lowercase().trim()
            val filteredItems = initialDataItems.filter { it.name?.lowercase()?.contains(queryName).isTrue() }
            return FilterResults().apply { values = filteredItems }
        }

        override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
            val values = results?.values as? List<*> ?: return
            val filteredValues = values.filterIsInstance(UiModel::class.java)
            if (values.size != filteredValues.size) return
            listDiffer.submitList(filteredValues)
        }
    }

    override fun getItemCount() = listDiffer.currentList.size

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val view = parent.inflate(R.layout.item_layout)
        return CustomViewHolder(
            view = view,
            listener = { listener.onClickFavorite(uiModel) }
        )
    }

    override fun onBindViewHolder(
        holder: RecyclerView.ViewHolder,
        position: Int,
        payloads: MutableList<Any>
    ) {
        val payloadValid = payloads.isNotEmpty() && payloads[0] is UiModel
        if (payloadValid && holder is CustomViewHolder) {
            holder.updatePayload(payloads[0] as UpdateUiModel)
        } else {
            super.onBindViewHolder(holder, position, payloads)
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val item = getItem(position)
        (holder as? CustomViewHolder)?.bind(item)
    }

    override fun getFilter() = dataFilter

    fun submitList(data: List<UiModel>, updateInitialItems: Boolean = true) {
        listDiffer.submitList(data)
        if (updateInitialItems) {
            initialDataItems.clear()
            initialDataItems.addAll(data)
        }
    }

    fun updateItem(payload: UpdateUiModel) {
        if (payload.id == null) return
        listDiffer.currentList.forEachIndexed { index, dataItem ->
            if (dataItem.id == payload.id) {
                updateModel(index, payload)
                notifyItemChanged(index, payload)
            }
        }
    }

    private fun isCorrectPosition(position: Int) = position in 0 until itemCount

    private fun updateModel(index: Int, payload: UiModel) {
        val currentList = listDiffer.currentList.toMutableList()
        if (isCorrectPosition(index)) currentList[index] = currentList[index].updateModel(payload)
        submitList(currentList)
    }

    private fun getItem(position: Int) = listDiffer.currentList[position]
}

问题根源分析

  1. 原始数据源与显示列表不同步:修改收藏状态时只更新了当前筛选后的列表,initialDataItems(原始数据源)未同步更新,导致清除筛选后恢复原始数据,丢失修改状态。
  2. 更新逻辑冲突:updateModel中调用submitList触发全量Diff,同时又调用notifyItemChanged做局部更新,异步执行时会导致状态覆盖。
  3. Payload类型判断错误:onBindViewHolder中判断Payload是UiModel,但实际传入的是UpdateUiModel,导致局部更新逻辑未触发。

修复方案

1. 同步更新原始数据源和显示列表

修改updateItem方法,同时更新原始数据和当前显示列表,避免状态丢失:

fun updateItem(payload: UpdateUiModel) {
    payload.id ?: return
    // 先更新原始数据源
    initialDataItems.forEachIndexed { idx, item ->
        if (item.id == payload.id) {
            initialDataItems[idx] = item.updateModel(payload)
        }
    }
    // 再更新当前显示列表并触发局部更新
    val currentList = listDiffer.currentList.toMutableList()
    currentList.forEachIndexed { idx, item ->
        if (item.id == payload.id) {
            currentList[idx] = item.updateModel(payload)
            notifyItemChanged(idx, payload)
        }
    }
}

2. 移除冲突的全量更新逻辑

删除updateModel方法,避免全量Diff和局部更新的冲突:

// 移除该方法
// private fun updateModel(index: Int, payload: UiModel) {
//     val currentList = listDiffer.currentList.toMutableList()
//     if (isCorrectPosition(index)) currentList[index] = currentList[index].updateModel(payload)
//     submitList(currentList)
// }

3. 修正Payload类型判断

在onBindViewHolder中正确识别UpdateUiModel类型:

override fun onBindViewHolder(
    holder: RecyclerView.ViewHolder,
    position: Int,
    payloads: MutableList<Any>
) {
    val payloadValid = payloads.isNotEmpty() && payloads[0] is UpdateUiModel
    if (payloadValid && holder is CustomViewHolder) {
        holder.updatePayload(payloads[0] as UpdateUiModel)
        return // 避免调用super执行全量绑定
    }
    super.onBindViewHolder(holder, position, payloads)
}

4. 优化Filter结果传递

确保筛选结果是新的列表实例,让AsyncListDiffer正确识别变化:

override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
    val filteredValues = (results?.values as? List<UiModel>) ?: emptyList()
    listDiffer.submitList(filteredValues.toList()) // 传递新列表实例
}

5. 确保数据类不可变

UiModel设计为不可变数据类,updateModel返回新实例,避免引用传递导致的状态混乱:

data class UiModel(
    val id: String,
    val name: String?,
    val isFavorite: Boolean
) {
    fun updateModel(payload: UpdateUiModel): UiModel {
        return copy(isFavorite = payload.isFavorite ?: this.isFavorite)
    }
}

data class UpdateUiModel(
    val id: String?,
    val isFavorite: Boolean?
)

完整修复后的Adapter代码

class Adapter(
    private val listener: AdapterListener
): RecyclerView.Adapter<RecyclerView.ViewHolder>(), Filterable {

    private val diffCallback = object : DiffUtil.ItemCallback<UiModel>() {
        override fun areItemsTheSame(oldItem: UiModel, newItem: UiModel): Boolean {
            return oldItem.id == newItem.id
        }

        override fun areContentsTheSame(oldItem: UiModel, newItem: UiModel): Boolean {
            return oldItem.name == newItem.name &&
                    oldItem.id == newItem.id &&
                    oldItem.isFavorite == newItem.isFavorite
        }
    }

    private var listDiffer = AsyncListDiffer(this, diffCallback)
    private val initialDataItems = mutableListOf<UiModel>()
    private val dataFilter = object : Filter() {
        override fun performFiltering(constraint: CharSequence?): FilterResults {
            val queryRawName = constraint?.toString()
            return if (queryRawName.isNullOrEmpty()) {
                FilterResults().apply { values = initialDataItems.toList() }
            } else {
                val queryName = queryRawName.lowercase().trim()
                val filteredItems = initialDataItems.filter { 
                    it.name?.lowercase()?.contains(queryName) == true 
                }
                FilterResults().apply { values = filteredItems }
            }
        }

        override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
            val filteredValues = (results?.values as? List<UiModel>) ?: emptyList()
            listDiffer.submitList(filteredValues.toList())
        }
    }

    override fun getItemCount() = listDiffer.currentList.size

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val view = parent.inflate(R.layout.item_layout)
        return CustomViewHolder(
            view = view,
            listener = { listener.onClickFavorite(it) }
        )
    }

    override fun onBindViewHolder(
        holder: RecyclerView.ViewHolder,
        position: Int,
        payloads: MutableList<Any>
    ) {
        val payloadValid = payloads.isNotEmpty() && payloads[0] is UpdateUiModel
        if (payloadValid && holder is CustomViewHolder) {
            holder.updatePayload(payloads[0] as UpdateUiModel)
            return
        }
        super.onBindViewHolder(holder, position, payloads)
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val item = getItem(position)
        (holder as? CustomViewHolder)?.bind(item)
    }

    override fun getFilter() = dataFilter

    fun submitList(data: List<UiModel>, updateInitialItems: Boolean = true) {
        listDiffer.submitList(data.toList())
        if (updateInitialItems) {
            initialDataItems.clear()
            initialDataItems.addAll(data)
        }
    }

    fun updateItem(payload: UpdateUiModel) {
        payload.id ?: return
        // 更新原始数据源
        initialDataItems.forEachIndexed { idx, item ->
            if (item.id == payload.id) {
                initialDataItems[idx] = item.updateModel(payload)
            }
        }
        // 更新当前显示列表并触发局部更新
        val currentList = listDiffer.currentList.toMutableList()
        currentList.forEachIndexed { idx, item ->
            if (item.id == payload.id) {
                currentList[idx] = item.updateModel(payload)
                notifyItemChanged(idx, payload)
            }
        }
    }

    private fun getItem(position: Int) = listDiffer.currentList[position]

    interface AdapterListener {
        fun onClickFavorite(uiModel: UiModel)
    }
}

// 配套数据类示例
data class UiModel(
    val id: String,
    val name: String?,
    val isFavorite: Boolean
) {
    fun updateModel(payload: UpdateUiModel): UiModel {
        return copy(isFavorite = payload.isFavorite ?: this.isFavorite)
    }
}

data class UpdateUiModel(
    val id: String?,
    val isFavorite: Boolean?
)

内容的提问来源于stack exchange,提问作者Tim Meleshko

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 00:57:12