【问题标题】:How to use LazyColumn's animateItemPlacement() without autoscrolling on changes?如何在不自动滚动更改的情况下使用 LazyColumn 的 animateItemPlacement()?
【发布时间】:2022-12-04 02:08:57
【问题描述】:

我在类似清单的样式中使用LazyColumn。该列表首先显示所有待完成的项目,最后显示所有已完成的项目。点击一个项目会切换它是否完成。

这是我正在做的事情的 MWE:

data class TodoItem(val id: Int, val label: String, var isDone: Boolean)

@Composable
fun TodoCard(item: TodoItem, modifier: Modifier, onClick: () -> Unit) {
    val imagePainterDone = rememberVectorPainter(Icons.Outlined.Done)
    val imagePainterNotDone = rememberVectorPainter(Icons.Outlined.Add)

    Card(
        modifier
            .padding(8.dp)
            .fillMaxWidth()
            .clickable {
                onClick()
            }) {
        Row {
            Image(
                if (item.isDone) imagePainterDone else imagePainterNotDone,
                null,
                modifier = Modifier.size(80.dp)
            )
            Text(text = item.label)
        }
    }
}

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ExampleColumn() {
    val todoItems = remember {
        val list = mutableStateListOf<TodoItem>()
        for (i in 0..20) {
            list.add(TodoItem(i, "Todo $i", isDone = false))
        }
        list
    }
    val sortedTodoItems by remember {
        derivedStateOf { todoItems.sortedWith(compareBy({ it.isDone }, { it.id })) }
    }

    LazyColumn {
        items(sortedTodoItems, key = {it.label}) { item ->
            TodoCard(item = item, modifier = Modifier.animateItemPlacement()) {
                val index = todoItems.indexOfFirst { it.label == item.label }
                if (index < 0) return@TodoCard
                todoItems[index] = todoItems[index].copy(isDone = !todoItems[index].isDone)
            }
        }
    }
}

除了Modifier.animateItemPlacement() 引入的一个副作用外,这项工作很好:当切换第一个当前可见的列表元素时,LazyListState 将滚动以跟随该元素。

这不是我想要的(我更希望它保持在同一个索引处)。

我找到了this workaround,建议在第一个元素发生变化时滚动回第一个元素,但这仅在列的第一项是当前显示的第一个元素时才解决问题。如果向下滚动使得第三个元素出现在最顶部可见并点击该元素,该列仍将滚动以跟随它。

有没有办法将自动滚动与项目放置动画分离?两者似乎都依赖于 LazyColumnkey 参数?

【问题讨论】:

    标签: android-jetpack-compose android-animation lazycolumn


    【解决方案1】:

    我没有尝试编译/运行你的代码,但看起来你有类似this 的问题,看起来是因为这个在LazyListState

    /**
     * When the user provided custom keys for the items we can try to detect when there were
     * items added or removed before our current first visible item and keep this item
     * as the first visible one even given that its index has been changed.
     */
    internal fun updateScrollPositionIfTheFirstItemWasMoved(itemsProvider: LazyListItemsProvider) {
        scrollPosition.updateScrollPositionIfTheFirstItemWasMoved(itemsProvider)
    }
    

    我提出了一种可能的解决方法,即通过将其键设置为其索引而不是支持数据的唯一标识符来牺牲第一个项目的动画。

    itemsIndexed(
            items = checkItems.sortedBy { it.checked.value },
            key = { index, item -> if (index == 0) index else item.id }
       ) { index, entry ->
            ...
       }
    

    虽然我还没有使用这种惰性密钥设置来回归任何用例,也无法解决您的问题,但您可以考虑尝试一下。

    【讨论】:

    • 感谢您的见解。这似乎正是我想停用的内部功能。 :-) 您的解决方法解决了与我在提出问题之前找到的答案相同的边缘情况(第一项被修改)。假设我向下滚动直到第三个项目第一个可见。如果我现在点击它,它将向下移动,该列的可见部分将滚动以跟随它。
    猜你喜欢
    • 2020-04-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-23
    • 2011-08-17
    • 2012-01-08
    • 1970-01-01
    相关资源
    最近更新 更多