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

NotifyItemRemoved()异常:RecyclerView点击箭头误删非目标列表元素

RecyclerView删除列表项位置错误排查与修复

我有一个包含8个国家的列表,点击第一个项的箭头图标时,预期删除第二个国家(中国),但实际操作后被删除的却是第三个国家。

点击前的列表:
点击前的列表

点击后的列表:
点击后的列表

我的CountryAdapter代码如下:

class CountryAdapter(
    private val context: Context,
    private val activity: Activity,
    private val countries: MutableList<Countries>,
) :
    RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    private var isPressed = false
    private var deletedList: HashMap<Int, Countries> = HashMap()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val viewHolder: CountryViewHolder
        val inflater = LayoutInflater.from(parent.context)
        val holderView = inflater.inflate(R.layout.country_item, parent, false)
        viewHolder = CountryViewHolder(holderView)
        viewHolder.countryName = holderView.findViewById(R.id.countryName)
        viewHolder.arrowImg = holderView.findViewById(R.id.arrow)
        return viewHolder
    }
    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        holder.setIsRecyclable(false)
        val viewHolder = holder as CountryViewHolder
        val txt = viewHolder.itemView.findViewById<TextView>(R.id.countryName)
        val baseLayout = viewHolder.itemView.findViewById<LinearLayout>(R.id.base_linear_layout)
        val basedecLayout = viewHolder.itemView.findViewById<LinearLayout>(R.id.base_des_layout)
        val arrow = viewHolder.itemView.findViewById<ImageView>(R.id.arrow)
        if (position != 0) {
            arrow.visibility = View.GONE
        } else {
            basedecLayout.setOnClickListener {
                arrow.performClick()
            }
            arrow.setOnClickListener {
                if (!isPressed) {
                    for (i in 1..1) {
                        deletedList[i] = countries[1]
                        countries.removeAt(1)   //Problem is here
                        notifyItemRemoved(1)    // problem is here
                    }
                }
//                else {
//                    for (i in 3 downTo 1) {
//                        countries.add(1, deletedList[i]!!)
//                        notifyItemInserted(1)
//                    }
//                    deletedList.clear()
//                }
                isPressed = !isPressed
            }
        }
        txt.text = countries[position].name
    }

    override fun getItemCount(): Int {
        return countries.size
    }

    class CountryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        lateinit var countryName: TextView
        lateinit var arrowImg: ImageView
    }
}

问题原因

  1. 点击事件重复绑定:在onBindViewHolder中每次绑定holder时都会给箭头添加新的点击监听器,加上你设置了holder.setIsRecyclable(false)禁用了ViewHolder复用,导致同一个holder会被多次绑定点击事件。点击一次箭头会触发多次删除逻辑:第一次删除索引1的项(中国),第二次删除此时新的索引1项(原列表的第三个国家),最终表现为删除了第三个国家。
  2. 多余的循环逻辑:for (i in 1..1)完全是冗余代码,只会执行一次,却增加了代码复杂度。

修复方案

方案1:将点击事件绑定移至ViewHolder构造函数(推荐)

在ViewHolder初始化时就设置点击事件,避免重复绑定,同时通过回调处理删除逻辑:

class CountryAdapter(
    private val context: Context,
    private val activity: Activity,
    private val countries: MutableList<Countries>,
) :
    RecyclerView.Adapter<CountryAdapter.CountryViewHolder>() {

    private var isPressed = false
    private var deletedList: HashMap<Int, Countries> = HashMap()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CountryViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        val holderView = inflater.inflate(R.layout.country_item, parent, false)
        return CountryViewHolder(holderView) {
            if (!isPressed) {
                // 保存要删除的项
                deletedList[1] = countries[1]
                // 删除索引1的项
                countries.removeAt(1)
                notifyItemRemoved(1)
                // 通知后续项位置变化,避免显示异常
                notifyItemRangeChanged(1, countries.size - 1)
            }
            isPressed = !isPressed
        }
    }

    override fun onBindViewHolder(holder: CountryViewHolder, position: Int) {
        // 移除不必要的禁用复用逻辑,提升性能
        holder.countryName.text = countries[position].name
        holder.arrowImg.visibility = if (position == 0) View.VISIBLE else View.GONE
    }

    override fun getItemCount(): Int {
        return countries.size
    }

    class CountryViewHolder(
        itemView: View,
        private val onArrowClick: () -> Unit
    ) : RecyclerView.ViewHolder(itemView) {
        val countryName: TextView = itemView.findViewById(R.id.countryName)
        val arrowImg: ImageView = itemView.findViewById(R.id.arrow)
        private val basedecLayout: LinearLayout = itemView.findViewById(R.id.base_des_layout)

        init {
            // 在ViewHolder初始化时绑定点击事件,仅执行一次
            arrowImg.setOnClickListener {
                onArrowClick()
            }
            basedecLayout.setOnClickListener {
                arrowImg.performClick()
            }
        }
    }
}

方案2:清空旧监听器后再绑定(临时修复)

如果不想修改ViewHolder结构,可以在设置新的点击监听器前先移除旧的,同时删除冗余循环:

// 在arrow.setOnClickListener前添加
arrow.setOnClickListener(null)
arrow.setOnClickListener {
    if (!isPressed) {
        deletedList[1] = countries[1]
        countries.removeAt(1)
        notifyItemRemoved(1)
        notifyItemRangeChanged(1, countries.size - 1)
    }
    isPressed = !isPressed
}

额外优化建议

  • 尽量不要禁用ViewHolder复用(setIsRecyclable(false)),这会大幅降低RecyclerView的滚动性能,仅在特殊场景下使用。
  • 可以将deletedList改为MutableList<Countries>,比HashMap更适合存储被删除的列表项。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 06:22:06