获取 ArrayIndexOutOfBoundsException: length=10; index=-1 当我尝试撤消删除 RecyclerView 元素时

问题描述 投票:0回答:3

我有一个 RecyclerView 列表。我做了一个滑动删除。然后我在 MainActivity 中做了一个 Snackbar 来撤销删除:

val onSwipe = object : OnSwipe(this) {
    override fun onSwiped(viewHolder: ViewHolder, direction: Int) {
        when (direction) {
            ItemTouchHelper.RIGHT -> {
                adapter.removeItem(
                    viewHolder.absoluteAdapterPosition
                )
Snackbar.make(binding.rv, "Deleted", Snackbar.LENGTH_SHORT)
                    .apply {
                        setAction("Undo") {
                            adapter.restoreItem(
                                viewHolder.absoluteAdapterPosition)
                        }
                        show()
                    }
            }
        }
    }

}

适配器中的代码:

fun removeItem(pos: Int) {
    listArray.removeAt(pos)
    notifyItemRemoved(pos)
    }

    fun restoreItem(pos: Int) {
        listArray.add(pos, listArray[pos])
        notifyItemInserted(pos)
 }

当我进行撤消操作时,我的应用程序停止了,我在 Logcat 中看到了这一点:

java.lang.ArrayIndexOutOfBoundsException: length=10; index=-1
at java.util.ArrayList.get(ArrayList.java:439)
at com.example.databaselesson.recyclerView.ExpensesAdapter.restoreItem(ExpensesAdapter.kt:79)
at com.example.databaselesson.MainActivity2$onSwipe$1.onSwiped$lambda-1$lambda-0(MainActivity2.kt:391)
at com.example.databaselesson.MainActivity2$onSwipe$1.$r8$lambda$AhJR3pu-3ynwFvPp66LdaLyFdB0(Unknown Source:0)
at com.example.databaselesson.MainActivity2$onSwipe$1$$ExternalSyntheticLambda0.onClick(Unknown Source:4)

求助

如果您需要更多代码,请写信,我会发送给您

android kotlin android-recyclerview itemtouchhelper sectionedrecyclerviewadapter
3个回答
2
投票

当您删除项目并执行

notifyItemRemoved
时,用于显示该项目的
ViewHolder
将从列表中删除。因为它没有显示任何东西,它的
absoluteAdapterPosition
被设置为
NO_POSITION
,或者 -1:

退货

int

RecyclerView
的角度来看项目的适配器位置,如果它仍然存在于适配器中并绑定到有效项目。
NO_POSITION
如果项目已从适配器中移除
notifyDataSetChanged
已在最后一次布局传递后调用或 ViewHolder 已被回收。

所以当您点击撤消按钮时,

viewholder
将返回 -1,这不是您的数据列表的有效索引!

您可能应该存储要删除的实际位置:

override fun onSwiped(viewHolder: ViewHolder, direction: Int) {
    // get the position first, and store that value
    val position = viewHolder.absoluteAdapterPosition

    when (direction) {
        ItemTouchHelper.RIGHT -> {
            // using the position we stored
            adapter.removeItem(position)
            // you don't have to use apply here if you don't want - it's designed
            // to be chained (fluent interface where each call returns the Snackbar)
            Snackbar.make(binding.rv, "Deleted", Snackbar.LENGTH_SHORT)
                // using that fixed position value again
                .setAction("Undo") { adapter.restoreItem(position) }
                .show()
        }
    }
}

这样你就可以删除一个特定的项目位置,如果点击了撤消按钮,你可以使用 相同的位置值 来恢复它。你不依赖于正在使用的

ViewHolder
的状态。


还有这个:

fun restoreItem(pos: Int) {
    listArray.add(pos, listArray[pos])
    notifyItemInserted(pos)
}

好像没有恢复什么?它只是在同一位置插入项目

pos
的副本。由于您的
removeItem
实际上从列表中删除了该项目,因此除非您将其存储在某个地方,否则无法取回它。你可以有一个
lastDeletedItem
变量,你在
removeItem
中更新它
restoreItem
恢复:

var lastDeletedItem: Item? = null

fun removeItem(pos: Int) {
    // store the deleted item
    lastDeletedItem = listArray[pos]
    listArray.removeAt(pos)
    notifyItemRemoved(pos)
}

fun restoreItem(pos: Int) {
    // restore the last thing that was deleted at this position
    lastDeletedItem?.let {
        listArray.add(pos, it)
        notifyItemInserted(pos)
    }
}

但是你有一个地方被删除的项目,另一个位置(小吃店 lambda)所以你可能想把它们放在一起 - 将

lastDeletedPosition
存储在
removeItem
中并在
restoreItem 中引用它
(不要传递
pos
),或者让
restoreItem
在存储当前适配器位置时获取
pos
item
并在滑动回调中获取项目


1
投票

这里有两个问题

1st:在

viewHolder.absoluteAdapterPosition
之后调用
notifyItemRemoved
应返回-1

这与您的 Logcat 中的异常匹配,因为它告诉您您正在尝试从

listArray
.

获取索引=-1
val onSwipe = object : OnSwipe(this) {
    override fun onSwiped(viewHolder: ViewHolder, direction: Int) {
        when (direction) {
            ItemTouchHelper.RIGHT -> {
                adapter.removeItem(
                    viewHolder.absoluteAdapterPosition //<==Let's say position return 8
                )
            Snackbar.make(binding.rv, "Deleted", Snackbar.LENGTH_SHORT)
                    .apply {
                        setAction("Undo") {
                            adapter.restoreItem(
                                viewHolder.absoluteAdapterPosition) //<==Deselected item so it shall return -1
                        }
                        show()
                    }
            }
        }
    }

}

第二:您没有缓存项目对象,因此它将无法检索正确的数据

// Assume that `listArray` = ["A", "B", "C"], `pos` = 1
fun removeItem(pos: Int) {
    listArray.removeAt(pos) = ["A", "C"]
    notifyItemRemoved(pos)
}

// `listArray` = ["A", "C"], `pos` = 1 (Assume you get the correct target pos)
fun restoreItem(pos: Int) { 
    listArray.add(pos, listArray[pos]) //`listArray[1]` = "C", listArray = ["A", "C", "C"]
    notifyItemInserted(pos)
 }

为了解决这个问题,您需要在

onSwiped
调用中缓存位置和项目对象

val onSwipe = object : OnSwipe(this) {
    override fun onSwiped(viewHolder: ViewHolder, direction: Int) {
        when (direction) {
            ItemTouchHelper.RIGHT -> {
                val cachedPosition = viewHolder.absoluteAdapterPosition // cache position! 
                val cachedItem = listArray[cachedPosition] // cache item!
                adapter.removeItem(cachedPosition)

                Snackbar.make(binding.rv, "Deleted", Snackbar.LENGTH_SHORT)
                    .apply {
                        setAction("Undo") {
                            adapter.restoreItem(cachedPosition, cachedItem) 
                     }
                        show()
                    }
            }
        }
    }

}




0
投票

如果您使用的是 FlexLayoutManager,只需添加此代码

FlexboxLayoutManager flexboxLayoutManager = new FlexboxLayoutManager(mContext);
flexboxLayoutManager.setFlexDirection(FlexDirection.ROW);
flexboxLayoutManager.setJustifyContent(JustifyContent.FLEX_START);

并在 XML 中为 Recyclerview 添加固定高度

© www.soinside.com 2019 - 2024. All rights reserved.