Android RecyclerView中的拖动项如何与分页库一起工作?

Android RecyclerView中的拖动项如何与分页库一起工作?,android,android-recyclerview,android-paging,Android,Android Recyclerview,Android Paging,我的应用程序有一个RecyclerView,它支持拖动项目以更改其顺序。 我的应用程序在添加分页库之前使用ViewModel、Lifecycle和Room。处理拖动的代码也很简单 override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean { val oPosition = viewHol

我的应用程序有一个RecyclerView,它支持拖动项目以更改其顺序。 我的应用程序在添加分页库之前使用ViewModel、Lifecycle和Room。处理拖动的代码也很简单

override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
        val oPosition = viewHolder.adapterPosition
        val tPosition = target.adapterPosition
        Collections.swap(adapter?.data ,oPosition,tPosition)
        adapter?.notifyItemMoved(oPosition,tPosition)
        //save to db
        return true
    }
但是,在我使用分页库之后

override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
        val oPosition = viewHolder.adapterPosition
        val tPosition = target.adapterPosition
        Collections.swap(adapter.currentList,oPosition,tPosition)
        adapter.notifyItemMoved(oPosition,tPosition)
        return true
    }
我的应用程序崩溃,因为PagedListAdapter.currentList不支持设置

    java.lang.UnsupportedOperationException
    at java.util.AbstractList.set(AbstractList.java:132)
    at java.util.Collections.swap(Collections.java:539)
    at gmail.zebulon988.tasklist.ui.TaskListFragment$MyItemTouchCallback.onMove(TaskListFragment.kt:119).
然后我更改代码

override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
        val oPosition = viewHolder.adapterPosition
        val tPosition = target.adapterPosition
        Log.d("TAG","onMove:o=$oPosition,t=$tPosition")
        val oTask = (viewHolder as VH).task
        val tTask = (target as VH).task

        if(oTask != null && tTask != null){
            val tmp = oTask.order
            oTask.order = tTask.order
            tTask.order = tmp
            tasklistViewModel.insertTask(oTask,tTask)

        }
        return true
    }
此代码直接更改任务在db中的顺序,库通过db更改更新显示顺序。然而,动画是丑陋的


有没有一种方法可以优雅地将
onMove
paging library
一起使用?

当您使用带有文件室的页面列表时,您通常会将其捆绑起来,以便通过LiveData或Rx自动反映对底层数据的更新,而这种在后台发生的更新总是会打乱您的拖放操作。所以我认为你不可能在所有情况下都做到100%防弹。话虽如此,您可以创建一个垫片(我几乎说是“hack-together”),它可以实现您想要的功能。这涉及几个方面:

  • 您需要保存适配器中正在交换的项的索引
  • 您需要重写适配器中的getItem(),并使其为您“交换”项目,而不是使用Collections.swap进行交换
  • 您需要通过房间延迟实际的项目更新,直到项目被删除,此时您还可以清除“正在交换”状态。大致如下:

    fun swapItems(fromPosition: Int, toPosition: Int) {
        swapInfo = SwapInfo(fromPosition, toPosition)
        notifyItemMoved(fromPosition, toPosition)
    }
    
    override fun getItem(position: Int): T? {
        return swapInfo?.let {
            when (position) {
                it.fromPosition -> super.getItem(it.toPosition)
                it.toPosition -> super.getItem(it.fromPosition)
                else -> super.getItem(position)
            }
        } ?: super.getItem(position)
    }
    
    fun clearSwapInfo() {
        swapInfo = null
    }
    

  • 通过这种方式,只要列表没有背景更新,并且保持在已加载的项目列表中,您就可以获得平滑的拖动体验。如果您需要能够通过“重新填充”进行拖动,则会变得更加复杂。

    当您使用带有文件室的页面列表时,您通常会将其捆绑起来,以便通过LiveData或Rx自动反映对基础数据的更新,而在后台发生的此类更新总是会打乱您的拖放操作。所以我认为你不可能在所有情况下都做到100%防弹。话虽如此,您可以创建一个垫片(我几乎说是“hack-together”),它可以实现您想要的功能。这涉及几个方面:

  • 您需要保存适配器中正在交换的项的索引
  • 您需要重写适配器中的getItem(),并使其为您“交换”项目,而不是使用Collections.swap进行交换
  • 您需要通过房间延迟实际的项目更新,直到项目被删除,此时您还可以清除“正在交换”状态。大致如下:

    fun swapItems(fromPosition: Int, toPosition: Int) {
        swapInfo = SwapInfo(fromPosition, toPosition)
        notifyItemMoved(fromPosition, toPosition)
    }
    
    override fun getItem(position: Int): T? {
        return swapInfo?.let {
            when (position) {
                it.fromPosition -> super.getItem(it.toPosition)
                it.toPosition -> super.getItem(it.fromPosition)
                else -> super.getItem(position)
            }
        } ?: super.getItem(position)
    }
    
    fun clearSwapInfo() {
        swapInfo = null
    }
    

  • 通过这种方式,只要列表没有背景更新,并且保持在已加载的项目列表中,您就可以获得平滑的拖动体验。如果您需要能够拖动“重新填充”,则会变得更加复杂。

    您需要检查页面列表中的移动项目

    如果你想上下拖动物品来移动它们,Recyclerview的适配器需要完美地完成两件事。第一个是交换datalist中的两个项,第二个是通知单元格重新呈现

    重新渲染很容易,移动时可以使用
    notifyItemMoved
    更新布局,但PagedList是不可变的,您无法修改它

    当cell ui已经更改但数据源没有更改时,会出现动画错误。您不能覆盖recyclerview内部的渲染逻辑,但可以检查
    PagedStorageDiffHelper.computeDiff的结果以修复动画错误

    最后,不要忘记在拖放之后检索最新的数据

    //ItemTouchHelperAdapter
    
    override fun onItemStartMove() {
        //the most the most updated data; mimic pagedlist, but can be modified;
        tempList = adapter.currentList?.toMutableList()
        toUpdate = mutableListOf()
    }
    
    override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
        val itemFrom = tempList?.get(fromPosition) ?: return false
        val itemTo = tempList?.get(toPosition) ?: return false
    
        //change order property for data itself
        val order = itemTo.order
        itemTo.order = itemFrom.order
        itemFrom.order = order
    
        //save them for later update db in batch
        toUpdate?.removeAll { it.id == itemFrom.id || it.id == itemTo.id }
        toUpdate?.add(itemFrom)
        toUpdate?.add(itemTo)
    
        //mimic mutable pagedlist, for get next time get correct items for continuing drag
        Collections.swap(tempList!!, fromPosition, toPosition)
    
        //update ui
        adapter.notifyItemMoved(fromPosition, toPosition)
    
        return true
    }
    
    override fun onItemEndMove() {
        tempList = null
    
        if (!toUpdate.isNullOrEmpty()) {
            mViewModel.viewModelScope.launch(Dispatchers.IO) {
    
                //heck, fix animation bug because pagedList did not really change.
                NoteListAdapter.disableAnimation = true
    
                mViewModel.updateInDB(toUpdate!!)
    
                toUpdate = null
            }
        }
    }
    
    //PagedListAdapter
    伴星{
    //选中“拖放”以移动页面列表中的项目
    var disableAnimation=false
    private val DiffCallback=对象:DiffUtil.ItemCallback(){
    覆盖乐趣项相同(旧:注释,新:注释):布尔值{
    返回disableAnimation | | old.id==aNew.id
    }
    覆盖内容相同(旧:注释,新:注释):布尔值{
    返回disableAnimation | | old==重新
    }
    }
    }
    
    您需要检查页面列表中的移动项目

    如果你想上下拖动物品来移动它们,Recyclerview的适配器需要完美地完成两件事。第一个是交换datalist中的两个项,第二个是通知单元格重新呈现

    重新渲染很容易,移动时可以使用
    notifyItemMoved
    更新布局,但PagedList是不可变的,您无法修改它

    当cell ui已经更改但数据源没有更改时,会出现动画错误。您不能覆盖recyclerview内部的渲染逻辑,但可以检查
    PagedStorageDiffHelper.computeDiff的结果以修复动画错误

    最后,不要忘记在拖放之后检索最新的数据

    //ItemTouchHelperAdapter
    
    override fun onItemStartMove() {
        //the most the most updated data; mimic pagedlist, but can be modified;
        tempList = adapter.currentList?.toMutableList()
        toUpdate = mutableListOf()
    }
    
    override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
        val itemFrom = tempList?.get(fromPosition) ?: return false
        val itemTo = tempList?.get(toPosition) ?: return false
    
        //change order property for data itself
        val order = itemTo.order
        itemTo.order = itemFrom.order
        itemFrom.order = order
    
        //save them for later update db in batch
        toUpdate?.removeAll { it.id == itemFrom.id || it.id == itemTo.id }
        toUpdate?.add(itemFrom)
        toUpdate?.add(itemTo)
    
        //mimic mutable pagedlist, for get next time get correct items for continuing drag
        Collections.swap(tempList!!, fromPosition, toPosition)
    
        //update ui
        adapter.notifyItemMoved(fromPosition, toPosition)
    
        return true
    }
    
    override fun onItemEndMove() {
        tempList = null
    
        if (!toUpdate.isNullOrEmpty()) {
            mViewModel.viewModelScope.launch(Dispatchers.IO) {
    
                //heck, fix animation bug because pagedList did not really change.
                NoteListAdapter.disableAnimation = true
    
                mViewModel.updateInDB(toUpdate!!)
    
                toUpdate = null
            }
        }
    }
    
    //PagedListAdapter
    伴星{
    //选中“拖放”以移动页面列表中的项目
    var disableAnimation=false
    private val DiffCallback=对象:DiffUtil.ItemCallback(){
    覆盖乐趣项相同(旧:注释,新:注释):布尔值{
    返回disableAnimation | | old.id==aNew.id
    }
    覆盖内容相同(旧:注释,新:注释):布尔值{
    返回disableAnimation | | old==重新
    }
    }
    }