如何在RecyclerView中拖动项目与分页库一起使用?

时间:2018-06-21 17:48:54

标签: android android-recyclerview android-paging

我的应用程序具有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不支持set。

    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

2 个答案:

答案 0 :(得分:0)

当您将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
    }
    

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

答案 1 :(得分:0)

您需要在PagedList中移动项目。

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

重新渲染很容易,移动时可以使用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
        }
    }
}