У меня есть список RecyclerView. И сделал удаление свайпом. Затем я сделал Snackbar в MainActivity, чтобы отменить удаление:
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)
Пожалуйста помоги
Если вам нужно больше кода, пожалуйста, напишите, и я пришлю вам его
Здесь есть две проблемы.
1-й: Позвоните viewHolder.absoluteAdapterPosition
после того, как notifyItemRemoved
вернется -1
Это соответствует исключению в вашем Logcat, поскольку оно говорит вам, что вы пытаетесь получить index=-1 из listArray
.
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()
}
}
}
}
}
2-й: вы не кэшировали объект элемента, поэтому он не сможет получить правильные данные
// 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()
}
}
}
}
}
Когда вы удаляете элемент и делаете 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)
}
}
Но тогда у вас есть элемент, который был удален в одном месте, а позиция в другом (лямбда закусочной), поэтому вы можете просто соединить их вместе - сохраните lastDeletedPosition
в removeItem
и укажите это в restoreItem
(не пропускайте pos
in), или заставьте restoreItem
взять pos
и item
и получить элемент в обратном вызове смахивания, когда вы сохраняете текущую позицию адаптера
Предположим, что код псевдо, так как я не эксперт по Котлину. Может быть какая-то синтаксическая ошибка. Но концепция заключается в том, что вы должны кэшировать вещь, прежде чем удалить ее, если вы хотите восстановить рано или поздно.