通过拖放重新排序 LazyColumn 项目
Reorder LazyColumn items with drag & drop
我想创建一个 LazyColumn
,其中包含可以通过拖放重新排序的项目。如果没有 compose,我的方法是使用 ItemTouchHelper.SimpleCallback
,但我还没有找到类似的东西用于 compose。
我试过使用 Modifier.longPressDragGestureFilter
and Modifier.draggable
,但这只允许我使用偏移量来拖动卡片。它没有给我一个列表索引(比如 ItemTouchHelper.SimpleCallback
中的 fromPosition
/toPosition
),我需要交换列表中的项目。
是否有与 ItemTouchHelper.SimpleCallback
的 onMove
功能等效的 compose 功能?如果不是,它是计划中的功能吗?
是否possible/feasible自己尝试实现这种事情?
到目前为止,据我所知,Compose 还没有提供处理此问题的方法,尽管我假设这将在工作中进行,因为他们已经添加了 draggable 和 longPressDragGestureFilter 修饰符,正如您已经提到的那样。当他们添加这些内容时,也许这是在惰性列中拖放的先兆。
2021 年 2 月 Google 提出了一个问题,他们的回应是他们不会为 1.0 版本提供官方解决方案,尽管他们在其中提供了一些关于如何处理该解决方案的指导。看起来目前最好的解决方案是使用带有 ItemTouchHelper 的 RecyclerView。
这是提到的问题:https://issuetracker.google.com/issues/181282427
21 年 11 月 23 日更新
虽然这不处理项目本身的点击,它只处理动画,这是朝着内部拖动重新排序迈出的一步。他们添加了使用名为 Modifier.animateItemPlacement()
的新修饰符的选项,并在设置项目时提供了一个键。
https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.1.0-beta03
示例:
var list by remember { mutableStateOf(listOf("A", "B", "C")) }
LazyColumn {
item {
Button(onClick = { list = list.shuffled() }) {
Text("Shuffle")
}
}
items(list, key = { it }) {
Text("Item $it", Modifier.animateItemPlacement())
}
}
可以使用 detectDragGesturesAfterLongPress
和 rememberLazyListState
.
构建一个简单(不完美)的可重新排序列表
基本思想是向 LazyColumn 添加一个拖动手势修改器并检测我们自己拖动的项目,而不是为每个项目添加一个修改器。
val listState: LazyListState = rememberLazyListState()
...
LazyColumn(
state = listState,
modifier = Modifier.pointerInput(Unit) {
detectDragGesturesAfterLongPress(....)
使用 LazyListState 提供的布局信息查找项目:
var position by remember {
mutableStateOf<Float?>(null)
}
...
onDragStart = { offset ->
listState.layoutInfo.visibleItemsInfo
.firstOrNull { offset.y.toInt() in it.offset..it.offset + it.size }
?.also {
position = it.offset + it.size / 2f
}
}
每次拖动时更新位置:
onDrag = { change, dragAmount ->
change.consumeAllChanges()
position = position?.plus(dragAmount.y)
// Start autoscrolling if position is out of bounds
}
为了在滚动时支持重新排序,我们无法在 onDrag
中进行重新排序。
为此,我们创建了一个流程来在每次 position/scroll 更新时找到最近的项目:
var draggedItem by remember {
mutableStateOf<Int?>(null)
}
....
snapshotFlow { listState.layoutInfo }
.combine(snapshotFlow { position }.distinctUntilChanged()) { state, pos ->
pos?.let { draggedCenter ->
state.visibleItemsInfo
.minByOrNull { (draggedCenter - (it.offset + it.size / 2f)).absoluteValue }
}?.index
}
.distinctUntilChanged()
.collect { near -> ...}
更新拖动的项目索引并在您的 MutableStateList 中移动项目。
draggedItem = when {
near == null -> null
draggedItem == null -> near
else -> near.also { items.move(draggedItem, it) }
}
fun <T> MutableList<T>.move(fromIdx: Int, toIdx: Int) {
if (toIdx > fromIdx) {
for (i in fromIdx until toIdx) {
this[i] = this[i + 1].also { this[i + 1] = this[i] }
}
} else {
for (i in fromIdx downTo toIdx + 1) {
this[i] = this[i - 1].also { this[i - 1] = this[i] }
}
}
}
计算相对项偏移量:
val indexWithOffset by derivedStateOf {
draggedItem
?.let { listState.layoutInfo.visibleItemsInfo.getOrNull(it - listState.firstVisibleItemIndex) }
?.let { Pair(it.index, (position ?: 0f) - it.offset - it.size / 2f) }
}
然后可以用来将偏移应用到拖动的项目(不要使用项目键!):
itemsIndexed(items) { idx, item ->
val offset by remember {
derivedStateOf { state.indexWithOffset?.takeIf { it.first == idx }?.second }
}
Column(
modifier = Modifier
.zIndex(offset?.let { 1f } ?: 0f)
.graphicsLayer {
translationY = offset ?: 0f
}
)
....
}
可以找到示例实现 here
我想创建一个 LazyColumn
,其中包含可以通过拖放重新排序的项目。如果没有 compose,我的方法是使用 ItemTouchHelper.SimpleCallback
,但我还没有找到类似的东西用于 compose。
我试过使用 Modifier.longPressDragGestureFilter
and Modifier.draggable
,但这只允许我使用偏移量来拖动卡片。它没有给我一个列表索引(比如 ItemTouchHelper.SimpleCallback
中的 fromPosition
/toPosition
),我需要交换列表中的项目。
是否有与 ItemTouchHelper.SimpleCallback
的 onMove
功能等效的 compose 功能?如果不是,它是计划中的功能吗?
是否possible/feasible自己尝试实现这种事情?
到目前为止,据我所知,Compose 还没有提供处理此问题的方法,尽管我假设这将在工作中进行,因为他们已经添加了 draggable 和 longPressDragGestureFilter 修饰符,正如您已经提到的那样。当他们添加这些内容时,也许这是在惰性列中拖放的先兆。
2021 年 2 月 Google 提出了一个问题,他们的回应是他们不会为 1.0 版本提供官方解决方案,尽管他们在其中提供了一些关于如何处理该解决方案的指导。看起来目前最好的解决方案是使用带有 ItemTouchHelper 的 RecyclerView。
这是提到的问题:https://issuetracker.google.com/issues/181282427
21 年 11 月 23 日更新
虽然这不处理项目本身的点击,它只处理动画,这是朝着内部拖动重新排序迈出的一步。他们添加了使用名为 Modifier.animateItemPlacement()
的新修饰符的选项,并在设置项目时提供了一个键。
https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.1.0-beta03
示例:
var list by remember { mutableStateOf(listOf("A", "B", "C")) }
LazyColumn {
item {
Button(onClick = { list = list.shuffled() }) {
Text("Shuffle")
}
}
items(list, key = { it }) {
Text("Item $it", Modifier.animateItemPlacement())
}
}
可以使用 detectDragGesturesAfterLongPress
和 rememberLazyListState
.
基本思想是向 LazyColumn 添加一个拖动手势修改器并检测我们自己拖动的项目,而不是为每个项目添加一个修改器。
val listState: LazyListState = rememberLazyListState()
...
LazyColumn(
state = listState,
modifier = Modifier.pointerInput(Unit) {
detectDragGesturesAfterLongPress(....)
使用 LazyListState 提供的布局信息查找项目:
var position by remember {
mutableStateOf<Float?>(null)
}
...
onDragStart = { offset ->
listState.layoutInfo.visibleItemsInfo
.firstOrNull { offset.y.toInt() in it.offset..it.offset + it.size }
?.also {
position = it.offset + it.size / 2f
}
}
每次拖动时更新位置:
onDrag = { change, dragAmount ->
change.consumeAllChanges()
position = position?.plus(dragAmount.y)
// Start autoscrolling if position is out of bounds
}
为了在滚动时支持重新排序,我们无法在 onDrag
中进行重新排序。
为此,我们创建了一个流程来在每次 position/scroll 更新时找到最近的项目:
var draggedItem by remember {
mutableStateOf<Int?>(null)
}
....
snapshotFlow { listState.layoutInfo }
.combine(snapshotFlow { position }.distinctUntilChanged()) { state, pos ->
pos?.let { draggedCenter ->
state.visibleItemsInfo
.minByOrNull { (draggedCenter - (it.offset + it.size / 2f)).absoluteValue }
}?.index
}
.distinctUntilChanged()
.collect { near -> ...}
更新拖动的项目索引并在您的 MutableStateList 中移动项目。
draggedItem = when {
near == null -> null
draggedItem == null -> near
else -> near.also { items.move(draggedItem, it) }
}
fun <T> MutableList<T>.move(fromIdx: Int, toIdx: Int) {
if (toIdx > fromIdx) {
for (i in fromIdx until toIdx) {
this[i] = this[i + 1].also { this[i + 1] = this[i] }
}
} else {
for (i in fromIdx downTo toIdx + 1) {
this[i] = this[i - 1].also { this[i - 1] = this[i] }
}
}
}
计算相对项偏移量:
val indexWithOffset by derivedStateOf {
draggedItem
?.let { listState.layoutInfo.visibleItemsInfo.getOrNull(it - listState.firstVisibleItemIndex) }
?.let { Pair(it.index, (position ?: 0f) - it.offset - it.size / 2f) }
}
然后可以用来将偏移应用到拖动的项目(不要使用项目键!):
itemsIndexed(items) { idx, item ->
val offset by remember {
derivedStateOf { state.indexWithOffset?.takeIf { it.first == idx }?.second }
}
Column(
modifier = Modifier
.zIndex(offset?.let { 1f } ?: 0f)
.graphicsLayer {
translationY = offset ?: 0f
}
)
....
}
可以找到示例实现 here