Jetpack Compose:在事件中滚动到列表底部

5

我有一个表示结果列表的可组合对象:

@Composable
fun ResultsList(results: List<Pair<Square, Boolean>>) {

    val coroutineScope = rememberCoroutineScope()
    val listState = rememberLazyListState()
    LazyRow(state = listState) {
        items(results) { result ->
            ResultsItem(result.first, result.second)
            coroutineScope.launch {
                listState.animateScrollToItem(results.size)
            }
        }
    }
}

期望行为: 当新项目添加时,列表应平滑滚动到最后一项

实际行为: 一切都很好,但当我快速手动滚动列表时,它也会自动放置在底部。而且,滚动不够流畅。

3个回答

11
您的代码出现以下错误:

调用launch应该在LaunchedEffect中而不是组合函数中发生。

您不应通过直接从Composable函数调用副作用来忽略它。有关更多详细信息,请参见副作用文档
相反,您可以像此错误建议的那样使用 LaunchedEffect 。通过将 results.size 作为键提供,您保证仅在列表大小更改时调用一次:
@Composable
fun ResultsList(results: List<Pair<Square, Boolean>>) {
    val listState = rememberLazyListState()
    LaunchedEffect(results.size) {
        listState.animateScrollToItem(results.size)
    }
    LazyRow(state = listState) {
        items(results) { result ->
            ResultsItem(result.first, result.second)
        }
    }
}

2
菲利普的解决方案对您有用。但是,我发布此内容是为了确保您了解以下原因:
A. 滚动不平稳。 B. 当您快速滚动列表时,它会滚动到底部。
A. 的解释: 这是因为您正在使用 animateScollTo。如果调用次数过多,我曾经遇到过该方法出现问题。
这个问题的解释在于Lazy scrollers如何在内部处理其子项。您知道,Lazy scrollers只显示大数据集的一个小窗口以供用户查看。为了实现这一点,它使用内部缓存。所以,屏幕上的项目和当前窗口顶部和底部的几个项目都被缓存。
现在,由于在您的代码中,在Composable的主体(即items范围)内进行了一次animateScrollTo(size)调用,因此代码将在每次组合时执行。
因此,在每次重新组合时,animateScrollTo方法和用户触摸输入之间存在活动冲突。当用户以不太快的方式滚动时,会发生以下情况-用户按下,轻轻滚动,然后松开手指。现在,请记住,只要手指按下,他们就会感觉到animateScrollTo没有效果(因为用户正在主动保持滚动条的位置,因此系统不会将其滚过)。因此,在用户滚动时,列表前面的一些项目被缓存,但是animateScrollTo不起作用。然后,由于运动足够缓慢,因此惯性造成的滚动距离不是问题,因为列表已经有足够的缓存项可以显示距离。这也解释了第二个问题。
B. 的解释: 当您快速滚动列表时,与上述情况(慢滚动)完全相同。只是这一次,惯性使列表向前滚动得太快,以至于无法基于内部缓存处理滚动条,因此存在活动重新组合。但是,现在由于没有活动的用户输入(他们已经将手指从屏幕上拿开),因此它实际上会animate到底部,因为在这里animateScrollTo方法没有冲突。
只要按下手指,无论您滚动得多快,它都不会滚到底部(请测试!)
现在来解决实际问题。Philip的答案非常棒。唯一的问题是,如果开发人员也有一个删除项目的实现,这个方法可能行不通。因为只监测了列表的大小,所以当添加或删除项时,它将滚动到末尾。为了解决这个问题,我们实际上需要某种参考值。因此,您可以自己实现某些内容来提供一个布尔变量,用于确认是否已添加了一个项目,或者您可以使用以下内容。
@Composable
fun ResultsList(results: List<Pair<Square, Boolean>>) {
    //Right here, create a variable to remember the current size
    val currentSize by rememberSaveable { mutableStateOf (results.size) }
   //Now, extract a Boolean to be used as a key for LaunchedEffect
    var isItemAdded by mutableStateO(results.size > currentSize)

    LaunchedEffect (isItemAdded){ //Won't be called upon item deletion
        if(isItemAdded){
           listState.animateScrollToItem(results.size)
           currentSize = results.size
        }
    }

    val listState = rememberLazyListState()
    LazyRow(state = listState) {
        items(results) { result ->
            ResultsItem(result.first, result.second)
        }
    }
}

这样可以确保正常的行为。当然,如果有其他需要帮助的地方,请告诉我。


感谢详细的解释!不幸的是,即使使用了Philip的解决方案,滚动条似乎仍然不够平滑(或者平滑滚动速度过快,无法被视为平滑)-这只是事情的状态,还是我们可以以某种方式改变它? - Simon
考虑发布一个显示您滚动的 gif。或者,您所说的“不流畅”是什么意思? - Richard Onslow Roper
我认为现在不应该出现延迟。建议现在也可以在生产环境中使用,所以...不应该有问题。显然实现上出了些问题。 - Richard Onslow Roper

-1
很明显,你为什么要在LazyList中调用:listState.animateScrollToItem(results.size)?当然会导致极差的性能。在渲染项目时,不应该去搞滚动。把这行代码删掉吧。

不太有帮助、傲慢的回应。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接