Jetpack Compose 易犯错误之:在 LazyColumn 中访问 LazyListState
Posted fundroid_方卓
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Jetpack Compose 易犯错误之:在 LazyColumn 中访问 LazyListState相关的知识,希望对你有一定的参考价值。
我们在使用 LazyColumn 或者 LazyRow 时,应该避免在 LazyListScope 中访问 LazyListState,这可能会造成隐藏的性能问题,看下面的代码:
@Composable
fun VerticalList(items: List<String>, onReachedBottom: () -> Unit)
val listState = rememberLazyListState()
LazyColumn(state = listState) // LazyListScope
items(items)
Text(text = it)
if (listState.firstVisibleItemIndex + listState.layoutInfo
.visibleItemsInfo.size == listState.layoutInfo.totalItemsCount)
onReachedBottom()
代码中我们希望,当列表滚动到底部时,回调 onReachedBottom
处理一些业务。但这种写法会造成 content 的代码频繁重组,造成性能问题
原因分析
我们在 LazyColumn 的 content lambda 也就是 LazyListScope 通过访问了 listState.firstVisibleItemIndex
的访问判断当前列表滚动的位置
firstVisibleItemIndex
在 LazyListState
中的定义如下:
@Stable
class LazyListState constructor(
firstVisibleItemIndex: Int = 0,
firstVisibleItemScrollOffset: Int = 0
) : ScrollableState
/**
* The holder class for the current scroll position.
*/
private val scrollPosition =
LazyListScrollPosition(firstVisibleItemIndex, firstVisibleItemScrollOffset)
/**
* The index of the first item that is visible
*/
val firstVisibleItemIndex: Int get() = scrollPosition.observableIndex
//...
而 observableIndex
在 scrollPosition
中的定义如下:
internal class LazyListScrollPosition(
initialIndex: Int = 0,
initialScrollOffset: Int = 0
)
var index = DataIndex(initialIndex)
private set
var scrollOffset = initialScrollOffset
private set
private val indexState = mutableStateOf(index.value)
val observableIndex get() = indexState.value
//...
可见,observableIndex
指向了 indexState
这个 State 的值,由于 content 是一个 Composable 的 lambda,所以在 content 中对 observableIndex
的访问时也就订阅了 indexState 的变化。
当我们将 LazyListState 传给 LazyColumn / LazyRow 后,随着列表的滚动,这个状态会实时更新,这就造成了 content 的无效重组。
Compose 中很多想 LazyListState 这样的对象,被称为 State Holder ,它们本身虽然不是 State 类型,但是它们内部会聚合一些 State,目的是将状态管理逻辑集中管理,所以对这些对象的访问很有可能就是对内部某个 State 的订阅。 因此对他们的使用要格外小心。
如何解决
@Composable
fun VerticalList(items: List<String>, onReachedBottom: () -> Unit)
val listState = rememberLazyListState()
val isReachedBottom by remember
derivedStateOf
listState.firstVisibleItemIndex + listState.layoutInfo
.visibleItemsInfo.size == listState.layoutInfo.totalItemsCount
LaunchedEffect(Unit)
snapshotFlow isReachedBottom
.collect isReached ->
if (isReached)
onReachedBottom()
LazyColumn(state = listState)
items(items)
Text(text = it)
修改的代码如上,我们将判断 list 滚动的逻辑抽象为一个 isReachedBottom
状态,然后通过 snapshotFlow
单独定义其变化,这样避免 LazyColumn 的 content 的重组。snapshotFlow
可以订阅 State 的变化,并将其转换为 Flow 的数据流。
也许有人会问 derivedStateOf
的作用是什么?
/**
* Creates a [State] object whose [State.value] is the result of [calculation]. The result of
* calculation will be cached in such a way that calling [State.value] repeatedly will not cause
* [calculation] to be executed multiple times, but reading [State.value] will cause all [State]
* objects that got read during the [calculation] to be read in the current [Snapshot], meaning
* that this will correctly subscribe to the derived state objects if the value is being read in
* an observed context such as a [Composable] function.
*/
fun <T> derivedStateOf(calculation: () -> T): State<T> = DerivedSnapshotState(calculation)
从注释可以清楚知道,derivedStateOf 将 calculation
的结果返回为一个 State,对这个 State 的访问相当于对 calculation 内部出现的 State 的访问,当 calculation 内部的 State 发生变化时,访问 DerivedState 的 Composable 会重组。为了避免 derivedStateOf 重复构建,需要使用 remember 进行缓存
从效果上来说
val isReachedBottom by remember
derivedStateOf
listState.firstVisibleItemIndex + listState.layoutInfo
.visibleItemsInfo.size == listState.layoutInfo.totalItemsCount
等价于
val isReachedBottom = remember(listState.firstVisibleItemIndex)
listState.firstVisibleItemIndex + listState.layoutInfo
.visibleItemsInfo.size == listState.layoutInfo.totalItemsCount
但是前者的重组范围只局限在对 isReachedBottom
访问的 Composable,而后者的重组范围发生在对 listState.firstVisibleItemIndex
访问的 Composable ,所以前者性能更优。
以上是关于Jetpack Compose 易犯错误之:在 LazyColumn 中访问 LazyListState的主要内容,如果未能解决你的问题,请参考以下文章
Jetpack Compose学习 之 HelloWorld
Jetpack Compose学习 之 HelloWorld
Jetpack Compose - Canvas之BlendMode