删除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
相关产品推荐
相关产品推荐

