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 } }
问题原因
- 点击事件重复绑定:在
onBindViewHolder中每次绑定holder时都会给箭头添加新的点击监听器,加上你设置了holder.setIsRecyclable(false)禁用了ViewHolder复用,导致同一个holder会被多次绑定点击事件。点击一次箭头会触发多次删除逻辑:第一次删除索引1的项(中国),第二次删除此时新的索引1项(原列表的第三个国家),最终表现为删除了第三个国家。 - 多余的循环逻辑:
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
相关产品推荐
相关产品推荐

