1

我的应用程序有一个 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 更改更新显示顺序。但是,动画很丑。

有没有办法优雅地使用onMovepaging library一起使用?</p>

4

3 回答 3

3

当您将 PagedList 与 Room 一起使用时,您通常会将其捆绑起来,以便通过 LiveData 或 Rx 自动反映对基础数据的更新,而在后台发生的此类更新总是会弄乱您的拖放操作。所以恕我直言,你不能让它在所有情况下都 100% 防弹。话虽如此,你可以创建(我几乎说“一起破解”)一个可以做你想做的事情的垫片。这涉及到几个部分:

  1. 您需要在适配器中保存正在交换的项目的索引
  2. 您需要覆盖适配器中的 getItem() 并使其为您“交换”项目,而不是使用 Collections.swap 交换它们
  3. 您需要通过 Room 延迟实际的项目更新,直到该项目被丢弃,此时您还清除“正在进行交换”状态。这些方面的东西:

    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
    }
    

这样,只要您的列表没有后台更新并且您停留在已加载的项目列表中,您将获得流畅的拖动体验。如果您需要能够拖动“笔芯”,它会变得更加复杂。

于 2019-02-07T22:44:40.520 回答
1

您需要在 PagedList 中移动项目。

如果您想上下拖动项目以移动它们,Recyclerview 的适配器需要完美地完成两件事。首先是交换datalist中的两个项目,其次是通知单元格重新渲染。

重新渲染很简单,notifyItemMoved移动时可以用来更新布局,但是PagedList是不可变的,不能修改。

当单元格 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
        }
    }
}
//Fragment

mViewModel.data.observe(this.viewLifecycleOwner, Observer {
    adapter.submitList(it)

    //delay for fix PagedStorageDiffHelper.computeDiff running in background thread
    if (NoteListAdapter.disableAnimation) {
        mViewModel.viewModelScope.launch {
            delay(500)

            adapter.notifyDataSetChanged() //update viewholder's binding data
            NoteListAdapter.disableAnimation = false
        }
    }
})
//PagedListAdapter

companion object {
    //heck for drag and drop to move items in PagedList
    var disableAnimation = false

    private val DiffCallback = object : DiffUtil.ItemCallback<Note>() {

        override fun areItemsTheSame(old: Note, aNew: Note): Boolean {
            return disableAnimation || old.id == aNew.id
        }

        override fun areContentsTheSame(old: Note, aNew: Note): Boolean {
            return disableAnimation || old == aNew
        }
    }
}
于 2020-04-06T00:50:15.083 回答
0

我遇到了一个稍微不同的问题,经过数小时的调试, @dmapr的回答终于让我找到了解决方案。

对我来说,问题是我刚刚移动的项目在数据库更新并调用后突然跳回原来的位置submitData。基本上拖放操作被取消了,但是所有相关数据的顺序在数据库中都是正确的,如果我要打电话,notifyDataSetChanged()我会看到所有项目应该在哪里的真实列表。这对我有用:

class SomePagingAdapter(
    private val onItemMoveUpdate: (fromPos: Int, toPos: Int) -> Unit,
) : PagingDataAdapter<Model, SomePagingAdapter.ViewHolder>(diffUtil), ItemMoveCallback {

    companion object {
        private val diffUtil = /* ... */
    }

    private var swapInfo: SwapInfo? = null

    // viewHolder methods, etc.

    // Called in touch helper's onMove
    override fun onItemMove(fromPos: Int, toPos: Int) {
        notifyItemMoved(fromPos, toPos)
    }

    // Called in touch helper's clearView() to save the result of this drag and drop
    override fun onItemFinishedMove(fromPos: Int, toPos: Int) {
        swapInfo = SwapInfo(fromPos, toPos)
        onItemMoveUpdate(fromPos, toPos)
    }

    fun adjustRecentSwapPositions() {
        // "Undo" the notifyItemMoved we did before that messed up positions
        swapInfo?.let { swap ->
            notifyItemMoved(swap.toPos, swap.fromPos)
        }
        swapInfo = null
    }
}

interface ItemMoveCallback {
    fun onItemMove(fromPos: Int, toPos: Int)
    fun onItemFinishedMove(fromPos: Int, toPos: Int)
}

data class SwapInfo(val fromPos: Int, val toPos: int)

重要的submitData是暂停并adjustRecentSwapPositions在之后立即调用。如果您使用 RxJava,请注意这一点。

scope.launch {
    flow.collectLatest { pagingData ->
        adapter.submitData(pagingData)
        adapter.adjustRecentSwapPositions()
    }
}

它工作得很好,回收商的动画也很好。

于 2022-02-21T23:58:24.277 回答