如何修复RecyclerView Adapter中onRowMoved函数的索引越界异常
RecyclerView拖拽移动触发IndexOutOfBoundsException异常排查与修复
我在实现RecyclerView拖拽移动功能时,Adapter的onRowMoved函数触发了java.lang.IndexOutOfBoundsException异常,崩溃日志如下:
Fatal Exception: java.lang.IndexOutOfBoundsException: Index -1 out of bounds for length 8 at jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64) at jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:70) at jdk.internal.util.Preconditions.checkIndex(Preconditions.java:266) at java.util.Objects.checkIndex(Objects.java:359) at java.util.ArrayList.get(ArrayList.java:434) at java.util.Collections.swap(Collections.java:548) at com....newProduct.NewProductImagesAdapter.onRowMoved(NewProductImagesAdapter.kt:208) at com....newProduct.NewProduct$createImagesAdapter$touchHelper$1.onMove(NewProduct.kt:927) at androidx.recyclerview.widget.ItemTouchHelper.moveIfNecessary(ItemTouchHelper.java:891) at androidx.recyclerview.widget.ItemTouchHelper$2.onTouchEvent(ItemTouchHelper.java:390) at androidx.recyclerview.widget.RecyclerView.dispatchToOnItemTouchListeners(RecyclerView.java:3515) at androidx.recyclerview.widget.RecyclerView.onTouchEvent(RecyclerView.java:3713) at android.view.View.dispatchTouchEvent(View.java:14879)
当前实现的代码:
val touchHelper = ItemTouchHelper(object : ItemTouchHelper.Callback() { ... override fun onMove( recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder ): Boolean { return imagesAdapter?.onRowMoved(viewHolder, target) ?: false } }) fun onRowMoved( fromViewHolder: RecyclerView.ViewHolder, toViewHolder: RecyclerView.ViewHolder ): Boolean { val fromPosition = fromViewHolder.bindingAdapterPosition val toPosition = toViewHolder.bindingAdapterPosition val imagesSize = this.images.size if (fromPosition < imagesSize && toPosition < imagesSize) { if (fromPosition < toPosition) { for (i in fromPosition until toPosition) { Collections.swap(this.images, i, i + 1) } notifyItemMoved(fromPosition, toPosition) } else { for (i in fromPosition downTo toPosition + 1) { Collections.swap(this.images, i, i - 1) } notifyItemMoved(toPosition, fromPosition) } Handler().postDelayed({ notifyDataSetChanged() }, 1000) return true } return false }
问题分析
崩溃日志明确指出索引-1越界,说明fromPosition或toPosition的值为-1。bindingAdapterPosition在ViewHolder已被回收、或对应数据源项已被删除时,会返回-1。当前代码只判断了位置小于集合长度,未校验位置是否≥0,导致无效位置进入数据交换逻辑触发异常。
另外,循环调用Collections.swap移动元素效率较低;延迟调用notifyDataSetChanged完全多余——notifyItemMoved已能精准通知RecyclerView更新UI,全局刷新反而会破坏拖拽流畅性。
修复方案
- 先校验
fromPosition和toPosition的有效性,必须同时满足≥0且<imagesSize,且两个位置不相同,否则直接返回false - 简化数据移动逻辑:直接移除原位置元素并插入到目标位置,替代循环swap
- 移除不必要的
notifyDataSetChanged调用
修改后的代码:
val touchHelper = ItemTouchHelper(object : ItemTouchHelper.Callback() { ... override fun onMove( recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder ): Boolean { return imagesAdapter?.onRowMoved(viewHolder, target) ?: false } }) fun onRowMoved( fromViewHolder: RecyclerView.ViewHolder, toViewHolder: RecyclerView.ViewHolder ): Boolean { val fromPosition = fromViewHolder.bindingAdapterPosition val toPosition = toViewHolder.bindingAdapterPosition val imagesSize = this.images.size // 校验位置有效性,排除-1、超出范围或相同位置的情况 if (fromPosition in 0 until imagesSize && toPosition in 0 until imagesSize && fromPosition != toPosition) { // 直接移动元素,替代循环swap val movedItem = images.removeAt(fromPosition) images.add(toPosition, movedItem) // 精准通知UI更新 notifyItemMoved(fromPosition, toPosition) return true } return false }
内容的提问来源于stack exchange,提问作者Oğuzhan Türkmen
相关产品推荐
相关产品推荐

