使用 DiffUtil 在 RecyclerView 上添加拖放

Dan*_*aga 8 android listadapter android-recyclerview

我有一个从房间数据库更新的列表。我从 Room 收到更新后的数据作为新列表,然后将其传递给ListAdaptersubmitList获取更改的动画。

list.observe(viewLifecycleOwner, { updatedList ->
    listAdapter.submitList(updatedList)
})
Run Code Online (Sandbox Code Playgroud)

现在,我想为同一个 RecyclerView 添加拖放功能。我尝试使用它来实现它ItemTouchHelper。但是,notifyItemMoved()由于 ListAdapter 通过 更新其内容,因此无法正常工作submitList()

override fun onMove(
    recyclerView: RecyclerView,
    viewHolder: RecyclerView.ViewHolder,
    target: RecyclerView.ViewHolder
): Boolean {

    val from = viewHolder.bindingAdapterPosition
    val to = target.bindingAdapterPosition

    val list = itemListAdapter.currentList.toMutableList()
    Collections.swap(list, from, to)

    // does not work for ListAdapter
    // itemListAdapter.notifyItemMoved(from, to)

    itemListAdapter.submitList(list)

    return false
}
Run Code Online (Sandbox Code Playgroud)

拖放现在可以正常工作,但只有在缓慢拖动时,当拖动足够快时,我会得到不同且不一致的结果。

屏幕记录

这可能是什么原因?为使用 ListAdapter 的 RecyclerView 实现拖放功能的最佳方法是什么?

Dan*_*aga 5

我最终实现了一个新的适配器并使用它代替 ListAdapter,正如Martin Marconcini 的回答中提到的。我添加了两个单独的函数。一个用于接收来自 Room 数据库的更新(替换来自submitListListAdapter),另一个用于接收拖动时的每个位置变化

MyListAdapter.kt

class MyListAdapter(list: ArrayList<Item>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    // save instance instead of creating a new one every submit 
    // list to save some allocation time. Thanks to Martin Marconcini
    private val diffCallback = DiffCallback(list, ArrayList())

    fun submitList(updatedList: List<Item>) {
        diffCallback.newList = updatedList
        val diffResult = DiffUtil.calculateDiff(diffCallback)

        list.clear()
        list.addAll(updatedList)
        diffResult.dispatchUpdatesTo(this)
    }

    fun itemMoved(from: Int, to: Int) {
        Collections.swap(list, from, to)
        notifyItemMoved(from, to)
    }

}
Run Code Online (Sandbox Code Playgroud)

DiffCallback.kt

class DiffCallback(
    val oldList: List<Item>,
    var newList: List<Item>
) : DiffUtil.Callback() {

    override fun getOldListSize(): Int {
        return oldList.size
    }

    override fun getNewListSize(): Int {
        return newList.size
    }

    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        val oldItem = oldList[oldItemPosition]
        val newItem = newList[newItemPosition]
        return oldItem.id == newItem.id
    }

    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        val oldItem = oldList[oldItemPosition]
        val newItem = newList[newItemPosition]
        return compareContents(oldItem, newItem)
    }
}
Run Code Online (Sandbox Code Playgroud)

调用itemMoved每个位置变化:

override fun onMove(
    recyclerView: RecyclerView,
    viewHolder: RecyclerView.ViewHolder,
    target: RecyclerView.ViewHolder
): Boolean {
    val from = viewHolder.bindingAdapterPosition
    val to = target.bindingAdapterPosition
    itemListAdapter.itemMoved(from, to)

    // Update database as well if needed

    return true
}
Run Code Online (Sandbox Code Playgroud)

当从 Room 数据库接收更新时:

如果您还在每次位置更改时更新数据库以防止不必要的调用,您可能还想检查当前是否使用onSelectedChanged进行拖动submitList

list.observe(viewLifecycleOwner, { updatedList ->
    listAdapter.submitList(updatedList)
})
Run Code Online (Sandbox Code Playgroud)