【问题标题】:Reorder LazyColumn items with drag & drop通过拖放重新排序 LazyColumn 项目
【发布时间】:2020-11-19 13:50:02
【问题描述】:

我想创建一个LazyColumn,其中包含可以通过拖放重新排序的项目。如果没有 compose,我的方法是使用 ItemTouchHelper.SimpleCallback,但我还没有找到类似的 compose。

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

是否有相当于ItemTouchHelper.SimpleCallbackonMove 函数的组合?如果没有,这是计划中的功能吗?

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

【问题讨论】:

    标签: android android-jetpack-compose


    【解决方案1】:

    到目前为止,我可以告诉 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())
        }
    }
    

    【讨论】:

    • 这个很接近答案了,只需要实现拖动更新List,能否继续完善答案
    【解决方案2】:

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

    基本思路是在 LazyColumn 中添加一个拖动手势修饰符,并检测我们自己拖动的项目,而不是为每个项目添加一个修饰符。

       val listState: LazyListState = rememberLazyListState()
       ...
       LazyColumn(
            state = listState,
            modifier = Modifier.pointerInput(Unit) {
                detectDragGesturesAfterLongPress(....)
    

    使用 LazyListState 提供的 layoutInfo 查找项目:

    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 中进行重新排序。 为此,我们创建了一个流程以在每次位置/滚动更新时找到最近的项目:

    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找到一个示例实现

    【讨论】:

    • 你有你的实现的开源链接吗?这个解释让我无法理解
    • @saeednoshadi 答案最后有一个示例存储库的链接,你试过运行它吗?
    • @PhilipDukhov 是的。这是一个图书馆,但我不想使用图书馆。我想完整地实施它。否则,库代码很复杂
    • @PhilipDukhov 我使用了这个来源:(github.com/MakeItEasyDev/…)
    • @saeednoshadi 这是开源的,您可以获取源代码并根据需要进行修改。据我所知,主要代码在this folder
    猜你喜欢
    • 1970-01-01
    • 2019-05-27
    • 1970-01-01
    • 2018-01-25
    • 1970-01-01
    • 1970-01-01
    • 2020-04-05
    • 2014-07-05
    • 1970-01-01
    相关资源
    最近更新 更多