使用拖动和重新排序 LazyColumn 项目降低 [英] Reorder LazyColumn items with drag & drop

查看:30
本文介绍了使用拖动和重新排序 LazyColumn 项目降低的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想创建一个 LazyColumn,其中包含可以通过拖动和重新排序的项目降低.如果没有撰写,我的方法是使用 ItemTouchHelper.SimpleCallback,但我还没有找到类似的东西用于撰写.

我试过使用 Modifier.longPressDragGestureFilterModifier.draggable,但这仅允许我使用偏移量拖动卡片.它没有给我一个列表索引(比如 ItemTouchHelper.SimpleCallback 中的 fromPosition/toPosition),我需要在我的列表.

是否有与 ItemTouchHelper.SimpleCallbackonMove 函数等效的组合?如果不是,它是计划中的功能吗?

自己尝试实现这种事情是否可能/可行?

解决方案

可以使用 detectDragGesturesAfterLongPressrememberLazyListState 构建一个简单(不完美)的可重排序列表.>

基本思想是向 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 项目降低的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆