You need to enable JavaScript to run this app. 删除RecyclerView条目后ListAdapter未更新索引触发IndexOutOfBoundsException崩溃
优惠活动
大模型
产品
解决方案
定价
更多

删除RecyclerView条目后ListAdapter未更新索引触发IndexOutOfBoundsException崩溃

问题根因与解决方案

根因分析

核心问题出在NoteAdapter的onBindViewHolder方法中,设置点击监听时直接捕获了方法入参的静态position值:

  • 该值是条目完成绑定时刻的索引,后续列表发生删除、插入等变更时,未触发重新绑定的ViewHolder会一直持有旧的position值
  • 删除索引1的条目后,原索引2的条目移动到了索引1的位置,但该条目点击事件中还是传递旧的索引值2,最终触发数组越界崩溃

修复方案

第一步:修改Adapter点击事件逻辑,使用实时的adapterPosition

替换NoteAdapter.kt中onBindViewHolder的点击回调代码:

override fun onBindViewHolder(holder: NoteHolder, position: Int) {
    val currentNote: Note = getItem(position)
    holder.noteTitle.text = currentNote.title
    holder.noteDescription.text = currentNote.description

    // 单条目菜单按钮点击逻辑
    holder.noteOptions.setOnClickListener {
        // holder.adapterPosition是RecyclerView维护的实时索引,永远返回条目当前的真实位置
        val realPosition = holder.adapterPosition
        // 过滤ViewHolder已脱离RecyclerView的无效场景
        if (realPosition != RecyclerView.NO_POSITION) {
            optionsMenuClickListener.onOptionsMenuClicked(realPosition)
        }
    }
}

第二步(可选):增加崩溃防御逻辑

可以在NoteFragment.kt的showOptionsMenu方法开头增加边界校验,进一步避免异常:

private fun showOptionsMenu(position: Int) {
    // 边界校验,位置无效直接返回
    if (position < 0 || position >= noteAdapter.itemCount) {
        return
    }
    // 原有逻辑不变
    val popupMenu = PopupMenu(context, noteRecyclerView[position].findViewById(R.id.note_options))
    // ...其余代码保持不变
}

为什么之前调用notifyDataSetChanged无效?

notifyDataSetChanged会触发所有可见条目重新绑定,理论上可以更新静态position的值,但你调用时机大概率不对:

  • 你在调用noteViewModel.delete()后立刻调用了刷新方法,此时Room还没完成数据库操作,LiveData还没返回新的列表数据,Adapter的数据源还没变更,自然没有效果
  • 即便时机正确,使用adapterPosition才是官方推荐的规范写法,不需要依赖全量刷新也能保证位置正确

关于索引重置的补充说明

RecyclerView的Adapter本身不需要手动重置索引,adapterPosition是RecyclerView框架实时维护的属性,只要使用该属性获取位置,天然就是0、1、2这类连续的正确索引,不需要额外做索引重置操作。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.14 14:54:01