Gab*_*abi 6 android scroll drag-and-drop android-recyclerview
目前,我有一个 RecyclerView 实现新的 ListAdapter,使用 SubmitList 来区分元素并继续自动更新 UI。
最近,我必须使用众所周知的 ItemTouchHelper 来实现对列表的拖放。这是我的实现,非常简单:
class DraggableItemTouchHelper(private val adapter: DestinationsAdapter) : ItemTouchHelper.Callback() {
private val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
private val swipeFlags = 0
override fun isLongPressDragEnabled() = false
override fun isItemViewSwipeEnabled() = false
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
return makeMovementFlags(dragFlags, swipeFlags)
}
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
val oldPos = viewHolder.adapterPosition
val newPos = target.adapterPosition
adapter.swap(oldPos, newPos)
return true
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
}
Run Code Online (Sandbox Code Playgroud)
}
这是我在适配器内的交换函数:
fun swap(from: Int, to: Int) {
submitList(ArrayList(currentList).also {
it[from] = currentList[to]
it[to] = currentList[from]
})
}
Run Code Online (Sandbox Code Playgroud)
除了移动列表的第一个项目外,一切都运行良好。有时它表现得很好,但大多数时候(比如 90%),即使将其稍微移动到第二个项目上方(例如将第一个项目移动到第二个位置),它也会捕捉多个位置。新位置似乎是随机的,我无法弄清楚问题所在。
作为指导,我使用https://github.com/material-components/material-components-android示例来实现拖放,并且其(简单的)列表和布局效果很好。我的列表有点复杂,因为它位于视图分页器内,使用导航组件并且在该屏幕中将许多其他视图限制在一起,但我认为这不应该相关。
此时我什至不知道如何在网络上搜索这个问题了。我为此找到的最接近的解决方案可能是https://issuetracker.google.com/issues/37018279但在实现并具有相同的行为之后,我认为这是因为我使用 ListAdapter ,它不同并异步更新列表,当解决方案使用RecyclerView.Adapter,它使用notifyItemMoved和其他类似的方法。
切换到 RecyclerView.Adapter 并不是一个解决方案。
这似乎是 中的一个错误AsyncListDiffer,由ListAdapter. 我的解决方案允许您在需要时手动比较更改。然而,它相当老套,使用反射,并且可能不适用于未来的appcompat版本(我测试过的版本是1.3.0)。
由于mDiffer是私有的ListAdapter并且您需要直接使用它,因此您必须创建自己的ListAdapter实现(您可以复制原始源)。然后添加以下方法:
fun setListWithoutDiffing(list: List<T>) {
setOf("mList", "mReadOnlyList").forEach { fieldName ->
val field = mDiffer::class.java.getDeclaredField(fieldName)
field.isAccessible = true
field.set(mDiffer, list)
}
}
Run Code Online (Sandbox Code Playgroud)
此方法会默默地更改底层中的当前列表AsyncListDiffer,而不会触发任何差异submitList()。
生成的文件应如下所示:
package com.example.yourapp
import androidx.recyclerview.widget.AdapterListUpdateCallback
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.AsyncListDiffer.ListListener
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
abstract class ListAdapter<T, VH : RecyclerView.ViewHolder?> : RecyclerView.Adapter<VH> {
private val mDiffer: AsyncListDiffer<T>
private val mListener =
ListListener<T> { previousList, currentList -> onCurrentListChanged(previousList, currentList) }
protected constructor(diffCallback: DiffUtil.ItemCallback<T>) {
mDiffer = AsyncListDiffer(
AdapterListUpdateCallback(this),
AsyncDifferConfig.Builder(diffCallback).build()
).apply {
addListListener(mListener)
}
}
protected constructor(config: AsyncDifferConfig<T>) {
mDiffer = AsyncListDiffer(AdapterListUpdateCallback(this), config).apply {
addListListener(mListener)
}
}
fun setListWithoutDiffing(list: List<T>) {
setOf("mList", "mReadOnlyList").forEach { fieldName ->
val field = mDiffer::class.java.getDeclaredField(fieldName)
field.isAccessible = true
field.set(mDiffer, list)
}
}
open fun submitList(list: List<T>?) {
mDiffer.submitList(list)
}
fun submitList(list: List<T>?, commitCallback: Runnable?) {
mDiffer.submitList(list, commitCallback)
}
protected fun getItem(position: Int): T {
return mDiffer.currentList[position]
}
override fun getItemCount(): Int {
return mDiffer.currentList.size
}
val currentList: List<T>
get() = mDiffer.currentList
open fun onCurrentListChanged(previousList: List<T>, currentList: List<T>) {}
}
Run Code Online (Sandbox Code Playgroud)
现在您需要更改适配器实现以继承您的自定义ListAdapter而不是androidx.recyclerview.widget.ListAdapter.
最后,您需要更改适配器的swap()方法实现以使用setListWithoutDiffing()和notifyItemMoved()方法:
fun swap(from: Int, to: Int) {
setListWithoutDiffing(ArrayList(currentList).also {
it[from] = currentList[to]
it[to] = currentList[from]
})
notifyItemMoved(from, to)
}
Run Code Online (Sandbox Code Playgroud)
另一种解决方案是创建一个自定义AsyncListDiffer版本,让您无需反射即可执行相同操作,但这种方式似乎更简单。我还将提交一个功能请求,以支持开箱即用的手动比较,并使用 Google 问题跟踪器链接更新问题。
| 归档时间: |
|
| 查看次数: |
1595 次 |
| 最近记录: |