PhantomCosmos
PhantomCosmos

Reputation: 119

Unwanted animation due to conflict between ItemTouchHelper and DiffUtil

I am implementing a RecyclerView with drag and drop support. When an item is dropped, the index column of that item will be updated in the Room database to store the updated sorting.

The problem I am facing is, when I call the Room database update after dropping the item, because the list of items is a LiveData in the ViewModel and bound to the RecyclerView through Databinding, DiffUtil will recalculate item positions and contents immediately after, which 1. adds new unwanted animations and 2. sometimes the content is not refreshed properly.

ItemTouchHelper:

val helper = ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(
    ItemTouchHelper.UP or ItemTouchHelper.DOWN or
            ItemTouchHelper.START or ItemTouchHelper.END, 0
) {

    var dragFrom = -1
    var dragTo = -1

    override fun onMove(
        recyclerView: RecyclerView, selected: RecyclerView.ViewHolder,
        target: RecyclerView.ViewHolder
    ): Boolean {
        val from = selected.adapterPosition
        val to = target.adapterPosition

        if (dragFrom == -1) {
            dragFrom = from
        }
        dragTo = to
        recyclerView.adapter?.notifyItemMoved(from, to)

        return true
    }

    override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
    }

    override fun clearView(
        recyclerView: RecyclerView,
        viewHolder: RecyclerView.ViewHolder
    ) {

        if (dragFrom != -1 && dragTo != -1 && dragFrom != dragTo) {
            val fromId = myAdapter.getItemId(dragFrom)

            detailViewModel.updateItemIndex(fromId, calcNewIndex(dragFrom, dragTo))
        }

        super.clearView(recyclerView, viewHolder)
        dragFrom = -1
        dragTo = -1
    }
})
helper.attachToRecyclerView(binding.detailRecyclerview)

DiffUtil in MyAdapter:

class NoteDiffCallback : DiffUtil.ItemCallback<MyNote>() {
    override fun areItemsTheSame(oldItem: MyNote, newItem: MyNote): Boolean {
        return oldItem.noteId == newItem.noteId
        //return true (replacing this will mostly fix the ItemTouchHelper issues, but also removes other animations that I want, such as inserting)
    }

    override fun areContentsTheSame(oldItem: MyNote, newItem: MyNote): Boolean {
        return oldItem == newItem
    }
}

I want to change it so that DiffUtil does not interfere with ItemTouchHelper, but I still want to keep DiffUtil for the nice animation when a new note is inserted. Would appreciate suggestions.

Upvotes: 4

Views: 1130

Answers (1)

Bram Stoker
Bram Stoker

Reputation: 1252

The trick is to update the ListAdapter items in your ItemTouchHelper.Callback, so that the update from your Room database does nothing because the items are already equal.

class ItemTouchHelperCallback(val adapter: MyNoteAdapter) :
        ItemTouchHelper.Callback() { // you need a reference to your adapter

    ...
    
    override fun onMove(
        recyclerView: RecyclerView, selected: RecyclerView.ViewHolder,
        target: RecyclerView.ViewHolder
    ): Boolean {
        val from = selected.adapterPosition
        val to = target.adapterPosition

        if (dragFrom == -1) {
            dragFrom = from
        }
        dragTo = to
    
        val items = adapter.currentList.toMutableList()
        Collections.swap(items, from, to)
        adapter.submitList(items) // calls adapter.notifyItemMoved(), so we don't have to

        return true
    }

    ...
}

Upvotes: 5

Related Questions