LazyLayoutCacheWindow в Compose 1.13: prefetch в ленивых списках теперь настраивается окном
Лента с тяжёлыми карточками дёргается на быстром скролле по понятной причине: новый элемент нужно скомпоновать, измерить и нарисовать за один кадр, и на сложной карточке бюджет кадра кончается раньше. Ленивые списки и раньше готовили элементы заранее, но скромно: по умолчанию один элемент по ходу скролла, а ушедшие с экрана сразу выбрасывались.
В Compose 1.13 эту механику довели до конца. LazyLayoutCacheWindow стал стабильным в 1.13.0-alpha01, а в alpha03 от 9 сентября LazyListPrefetchStrategy со всей императивной обвязкой помечен устаревшим. 7 октября ветка вышла в бету. Вместо коллбэков планирования теперь описывается область вокруг вьюпорта: сколько готовить впереди и сколько держать позади.
LazyColumn(
cacheWindow = LazyLayoutCacheWindow(ahead = 300.dp, behind = 150.dp),
) {
items(feed, key = { it.id }) { post -> PostCard(post) }
}
Окно можно задать и долей вьюпорта через aheadFraction и behindFraction. Элементы из зоны ahead готовятся заранее по направлению скролла, а элементы из зоны behind не уничтожаются и быстро возвращаются, если пользователь листает обратно. Передача окна через rememberLazyListState в 1.13 устарела, теперь параметр задаётся прямо у LazyColumn, LazyRow и сеток.
Дефолт по-прежнему осторожный: позади ничего не хранится, а пока пользователь не скроллит, кеш не заполняется. Под флагом ComposeFoundationFlags.isUsingDynamicDefaultCacheWindowInLists дефолтнjе окно подстраивается под средний размер видимых элементов и держится в пределах от 10 до 50% вьюпорта.
Большое окно не означает одного тяжёлого кадра. С версии 1.10 prefetch по умолчанию использует pausable composition: компоновка заготовленного элемента разбивается на части и раскладывается по нескольким кадрам, и сильнее всего это заметно на карточках с долгой композицией.
Платить приходится в другом месте. Заготовленные элементы компонуются раньше, чем появляются на экране, поэтому LaunchedEffect и DisposableEffect внутри них тоже срабатывают раньше. Аналитика показов, повешенная на эффекты, начнёт считать карточки, которые никто не видел. Показы лучше привязывать к реальной видимости:
PostCard(
post = post,
modifier = Modifier.onVisibilityChanged(
minDurationMs = 500,
minFractionVisible = 0.5f
) { visible ->
if (visible) analytics.impression(post.id)
}
)
Коллбэк сработает, когда карточка видна хотя бы наполовину и продержалась на экране полсекунды.
Если в коде есть LazyListPrefetchStrategy, после перехода на 1.13 её стоит заменить на cacheWindow в самом списке. Размер окна лучше подбирать замерами: тот же Google предлагает начать, например, с 50% вьюпорта и сравнить несколько вариантов.
#mobilevkhub #compose
Лента с тяжёлыми карточками дёргается на быстром скролле по понятной причине: новый элемент нужно скомпоновать, измерить и нарисовать за один кадр, и на сложной карточке бюджет кадра кончается раньше. Ленивые списки и раньше готовили элементы заранее, но скромно: по умолчанию один элемент по ходу скролла, а ушедшие с экрана сразу выбрасывались.
В Compose 1.13 эту механику довели до конца. LazyLayoutCacheWindow стал стабильным в 1.13.0-alpha01, а в alpha03 от 9 сентября LazyListPrefetchStrategy со всей императивной обвязкой помечен устаревшим. 7 октября ветка вышла в бету. Вместо коллбэков планирования теперь описывается область вокруг вьюпорта: сколько готовить впереди и сколько держать позади.
LazyColumn(
cacheWindow = LazyLayoutCacheWindow(ahead = 300.dp, behind = 150.dp),
) {
items(feed, key = { it.id }) { post -> PostCard(post) }
}
Окно можно задать и долей вьюпорта через aheadFraction и behindFraction. Элементы из зоны ahead готовятся заранее по направлению скролла, а элементы из зоны behind не уничтожаются и быстро возвращаются, если пользователь листает обратно. Передача окна через rememberLazyListState в 1.13 устарела, теперь параметр задаётся прямо у LazyColumn, LazyRow и сеток.
Дефолт по-прежнему осторожный: позади ничего не хранится, а пока пользователь не скроллит, кеш не заполняется. Под флагом ComposeFoundationFlags.isUsingDynamicDefaultCacheWindowInLists дефолтнjе окно подстраивается под средний размер видимых элементов и держится в пределах от 10 до 50% вьюпорта.
Большое окно не означает одного тяжёлого кадра. С версии 1.10 prefetch по умолчанию использует pausable composition: компоновка заготовленного элемента разбивается на части и раскладывается по нескольким кадрам, и сильнее всего это заметно на карточках с долгой композицией.
Платить приходится в другом месте. Заготовленные элементы компонуются раньше, чем появляются на экране, поэтому LaunchedEffect и DisposableEffect внутри них тоже срабатывают раньше. Аналитика показов, повешенная на эффекты, начнёт считать карточки, которые никто не видел. Показы лучше привязывать к реальной видимости:
PostCard(
post = post,
modifier = Modifier.onVisibilityChanged(
minDurationMs = 500,
minFractionVisible = 0.5f
) { visible ->
if (visible) analytics.impression(post.id)
}
)
Коллбэк сработает, когда карточка видна хотя бы наполовину и продержалась на экране полсекунды.
Если в коде есть LazyListPrefetchStrategy, после перехода на 1.13 её стоит заменить на cacheWindow в самом списке. Размер окна лучше подбирать замерами: тот же Google предлагает начать, например, с 50% вьюпорта и сравнить несколько вариантов.
#mobilevkhub #compose