问题描述
我想创建一个 LazyColumn
,其中包含可以通过拖动和重新排序的项目降低.如果没有撰写,我的方法是使用 ItemTouchHelper.SimpleCallback代码>
,但我还没有找到类似的东西用于撰写.
我试过使用 Modifier.longPressDragGestureFilter
和 Modifier.draggable
,但这仅允许我使用偏移量拖动卡片.它没有给我一个列表索引(比如 ItemTouchHelper.SimpleCallback
中的 fromPosition
/toPosition
),我需要在我的列表.
是否有与 ItemTouchHelper.SimpleCallback
的 onMove
函数等效的组合?如果不是,它是计划中的功能吗?
自己尝试实现这种事情是否可能/可行?
可以使用 detectDragGesturesAfterLongPress
和 rememberLazyListState
构建一个简单(不完美)的可重排序列表.>
基本思想是向 LazyColumn 添加拖动手势修饰符,并检测我们自己的拖动项,而不是为每个项添加修饰符.
val listState: LazyListState = rememberLazyListState()...懒列(状态 = 列表状态,修饰符 = Modifier.pointerInput(Unit) {detectDragGesturesAfterLongPress(....)
使用 LazyListState 提供的 layoutInfo 查找项目:
var position by remember {mutableStateOf(null)}...onDragStart = { 偏移量 ->listState.layoutInfo.visibleItemsInfo.firstOrNull { offset.y.toInt() in it.offset..it.offset + it.size }?.还 {位置 = it.offset + it.size/2f}}
在每次拖动时更新位置:
onDrag = { change, dragAmount ->change.consumeAllChanges()位置 = 位置?.plus(dragAmount.y)//如果位置越界,则开始自动滚动}
为了支持滚动时重新排序,我们不能在 onDrag
中进行重新排序.为此,我们创建了一个流程来在每个位置/滚动更新中找到最近的项目:
var draggedItem by remember {mutableStateOf(null)}....快照流 { listState.layoutInfo }.combine(snapshotFlow { position }.distinctUntilChanged()) { state, pos ->pos?.let {draggedCenter ->state.visibleItemsInfo.minByOrNull { (draggedCenter - (it.offset + it.size/2f)).absoluteValue }}?.指数}.distinctUntilChanged().collect { 附近 ->...}
更新拖动的项目索引并移动 MutableStateList 中的项目.
draggedItem = when {附近 == 空 ->空值draggedItem == null ->靠近否则 ->Near.also { items.move(draggedItem, it) }}有趣的<T>MutableList.move(fromIdx: Int, toIdx: Int) {如果(toIdx > fromIdx){for (i 在 fromIdx 直到 toIdx) {this[i] = this[i + 1].also { this[i + 1] = this[i] }}} 别的 {for (i in fromIdx downTo toIdx + 1) {this[i] = this[i - 1].also { this[i - 1] = this[i] }}}}
计算相对项目偏移量:
val indexWithOffset byderivedStateOf {拖拽物品?.let { listState.layoutInfo.visibleItemsInfo.getOrNull(it - listState.firstVisibleItemIndex) }?.let { Pair(it.index, (position ?: 0f) - it.offset - it.size/2f) }}
然后可用于将偏移应用于拖动的项目(不要使用项目键!):
itemsIndexed(items) { idx, item ->val 偏移量记住 {派生状态{ state.indexWithOffset?.takeIf { it.first == idx }?.second }}柱子(修饰符 = 修饰符.zIndex(offset?.let { 1f } ?: 0f).graphicsLayer {平移Y = 偏移量?:0f})....}
可以在此处
找到示例实现I want to create a LazyColumn
with items that can be reordered by drag & drop. Without compose, my approach would be to use ItemTouchHelper.SimpleCallback
, but I haven't found anything like that for compose.
I've tried using Modifier.longPressDragGestureFilter
and Modifier.draggable
, but that merely allows me to drag the card around using an offset. It doesn't give me a list index (like fromPosition
/toPosition
in ItemTouchHelper.SimpleCallback
), which I need to swap the items in my list.
Is there a compose equivalent to ItemTouchHelper.SimpleCallback
's onMove
function? If not, is it a planned feature?
Is it possible/feasible to try and implement this sort of thing myself?
A simple(not perfect) reorderable list can be build by using detectDragGesturesAfterLongPress
and rememberLazyListState
.
The basic idea is to add a drag gesture modifier to the LazyColumn and detect the dragged item our self instead of adding a modifier per item.
val listState: LazyListState = rememberLazyListState()
...
LazyColumn(
state = listState,
modifier = Modifier.pointerInput(Unit) {
detectDragGesturesAfterLongPress(....)
Find the item using the layoutInfo provided by 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
}
}
Update the position on every drag :
onDrag = { change, dragAmount ->
change.consumeAllChanges()
position = position?.plus(dragAmount.y)
// Start autoscrolling if position is out of bounds
}
To support reording while scrolling we cant do the reording in onDrag
.For this we create a flow to find the nearest item on every position/scroll update :
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 -> ...}
Update the dragged item index and move the item in your 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] }
}
}
}
Calculate the relative item offset :
val indexWithOffset by derivedStateOf {
draggedItem
?.let { listState.layoutInfo.visibleItemsInfo.getOrNull(it - listState.firstVisibleItemIndex) }
?.let { Pair(it.index, (position ?: 0f) - it.offset - it.size / 2f) }
}
Which can then used to apply the offset to the dragged item (Don`t use item keys !) :
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
}
)
....
}
A sample implementation can be found here
这篇关于使用拖动和重新排序 LazyColumn 项目降低的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!