如何使用 LazyColumn 的 animateItemPlacement() 而不在更改时自动滚动?

问题描述 投票:0回答:2

我在类似清单的样式中使用

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 解决方法,建议在第一个元素发生变化时滚动回第一个元素,但这只能解决该列的第一项是当前显示的第一个项目的问题。如果向下滚动以使第三个元素位于最上面可见并点击该元素,则该列仍将滚动以跟随它。

有什么方法可以将自动滚动与项目放置动画解耦吗?两者似乎都依赖于

LazyColumn
key
参数?

android-jetpack-compose android-animation lazycolumn
2个回答
2
投票

我还没有尝试编译/运行你的代码,但看起来你有类似的问题,比如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 ->
        ...
   }

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


0
投票

我已经能够通过添加如下虚拟项目来规避此行为:

LazyRow(...){
    item(key = "0") {
    }
    itemsIndexed(...)
    { .... ->
       ...
    }
}

您仍然可以使用钥匙。

© www.soinside.com 2019 - 2024. All rights reserved.