#android #kotlin #android-recyclerview
#Android #kotlin #android-recyclerview
Вопрос:
У меня есть простой RecyclerView, и я просто добавил функцию перетаскивания с помощью ItemTouchHelper.Callback
. В clearView
функции я указываю своей модели представления изменить живые данные элементов, чтобы я мог сохранить их позже. Чтобы свести к минимуму вызовы сервера, я сохраняю список в Firestore в двух случаях:
- Метод ViewModel
onCleared
- При сохранении любого другого изменения (например, добавления / обновления / удаления элемента)
Первый случай работает хорошо, но во втором я сталкиваюсь со странной проблемой: я получаю другую анимацию перетаскивания и в итоге получаю как старые, так и новые значения для разных элементов (одно переопределяет неизмененный элемент) и в беспорядочном порядке. Когда я перемещаюсь по фрагменту и возвращаюсь обратно, порядок и значения соответствуют назначению (потому что они были правильно сохранены в Firestore и правильно перезагружены). Таким образом, я думал, что проблема заключалась в том, что LiveData была перезагружена, но теперь я вижу, что аналогичная проблема возникает, когда я переупорядочиваю, а затем прокручиваю, чтобы переупорядоченные элементы не отображались (когда я вернулся, элемент в конечной позиции переопределяется перетаскиваемым элементом, но элемент в начальная позиция также является перетаскиваемым элементом). Есть ли способ сообщить адаптеру сохранять порядок после каждого удаления? Потому что я думаю, что проблема возникает, когда он пытается отменить порядок, когда изменяются LiveData или элементы не видны… Кроме того, я думаю, что не до конца понимаю весь процесс, поэтому я был бы рад получить некоторые объяснения 🙂
Мой код:
class TalesTouchCallback(private val adapter: TalesAdapter) : ItemTouchHelper.Callback() {
// Keeps the starting position of the dragged item
var oldPosition: Int? = null
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder)
: Int {
// Specify the directions of movement
val dragFlags =
if (viewHolder.itemViewType == TalesAdapter.ITEM_VIEW_TYPE_ITEM)
ItemTouchHelper.UP or ItemTouchHelper.DOWN
else ItemTouchHelper.ACTION_STATE_IDLE // The header can't be moved
return makeMovementFlags(dragFlags, 0)
}
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
// Notify the adapter that an item is moved from x position to y position
if (target.adapterPosition > 0) // Allow dragging over the header without interruption
adapter.notifyItemMoved(viewHolder.adapterPosition, target.adapterPosition)
return true
}
override fun isLongPressDragEnabled(): Boolean {
return true
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
}
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
super.onSelectedChanged(viewHolder, actionState)
// Handle action state changes
viewHolder?.let { oldPosition = it.adapterPosition }
}
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
super.clearView(recyclerView, viewHolder)
oldPosition?.let {
if (it != viewHolder.adapterPosition) adapter.onRowMoved(
it,
viewHolder.adapterPosition
)
oldPosition = null
}
}
}
class TalesAdapter(private val listener: TalesListener) :
ListAdapter<TalesAdapter.DataItem, RecyclerView.ViewHolder>(TalesDiffCallback()) {
...
fun onRowMoved(fromPosition: Int, toPosition: Int) =
// Only the adapter knows about the header - so the positions in the actual list are -1
if (toPosition > 0) listener.onMoved(fromPosition - 1, toPosition - 1) else null
class TalesDiffCallback : DiffUtil.ItemCallback<DataItem>() {
override fun areItemsTheSame(oldItem: DataItem, newItem: DataItem): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: DataItem, newItem: DataItem): Boolean {
return oldItem == newItem
}
}
sealed class DataItem {
abstract val id: String
data class TaleItem(val model: TaleItemModel) : DataItem() {
override val id = model.id
}
object Header : DataItem() {
override val id = ""
}
}
companion object {
const val ITEM_VIEW_TYPE_HEADER = 0
const val ITEM_VIEW_TYPE_ITEM = 1
}
}
class TalesViewModel : BaseViewModel(), TalesAdapter.TalesListener {
// The user's tales list
val tales: LiveData<MutableList<TaleItemModel>> = Database.userTales
// The tale currently being edited or deleted
private var clickedTale: TaleItemModel? = null
// Did the user change the tales order?
private var wasOrderChanged = false
// Types of supported events
sealed class EventType() : Event {
class ShowEditTaleDialog(val taleTitle: String) : EventType()
class ShowDeleteTaleDialog(val taleTitle: String) : EventType()
object ShowAddTaleDialog : EventType()
}
fun onAdd() = postEvent(EventType.ShowAddTaleDialog)
override fun onEdit(taleItem: TaleItemModel) {
clickedTale = taleItem
postEvent(EventType.ShowEditTaleDialog(taleItem.title))
}
override fun onDelete(taleItem: TaleItemModel) {
clickedTale = taleItem
postEvent(EventType.ShowDeleteTaleDialog(taleItem.title))
}
fun clearClickedTale() {
clickedTale = null
}
fun addTale(title: String) = Database.createTale(title)?.withProgressBar()
override fun onMoved(fromPosition: Int, toPosition: Int) {
// Move the tale to it's right place in the LiveData list
tales.value?.let {
if (fromPosition < toPosition) {
for (i in fromPosition until toPosition) Collections.swap(it, i, i 1)
} else {
for (i in fromPosition downTo toPosition 1) Collections.swap(it, i, i - 1)
}
}
wasOrderChanged = true
}
fun updateTale(title: String) =
// Copy is needed because if we change the original item, adapter's new list and old list would
// be the same and it will not refresh. Thus a copy is needed.
clickedTale?.copy(title = title)
?.let {
Database.updateTale(it)?.withProgressBar()
?.addOnCompleteListener { clearClickedTale() }
}
fun deleteTale() =
clickedTale
?.let {
Database.deleteTale(it.id)?.withProgressBar()
?.addOnCompleteListener { clearClickedTale() }
}
override fun onCleared() {
super.onCleared()
if (wasOrderChanged) Database.saveTalesOrder()?.withProgressBar()
}
}
( Database
Класс вызывает функции Firestore. При настройке / удалении он также берет список из Database.userTales
LiveData, поэтому сохраняет правильный порядок)
Любая помощь будет принята 🙂