Jetpack ComposeのLazyColumnで効率的なScroll Listenerの実装方法
概要
Jetpack Composeでのスクロールリスナー的なものを何も考えずに実装したら、何度も呼ばれちゃって非効率だったので、それをちゃんと書くための記事です。
関係する知識は以下です。この説明になります。
- rememberLazyListState()
- LaunchedEffect
- snapshotFlow
- derivedStateOf
スクロールしたときに何らかの条件によってログを送信するサンプル実装で説明します。
スクロールの状態の取得
rememberLazyListState()
まずは、スクロールの状態を取得するための実装です。
rememberLazyListState()の結果を LazyColumnに渡すだけです。
これで、変数listStateにスクロールの状態を保持できます。
val listState = rememberLazyListState()
LazyColumn(state = listState) {
// ...
}
無駄な再コンポーズを避ける実装
LaunchedEffect
普通にlistState.layoutInfoにアクセスすると再コンポーズを永遠にし続けます。
LazyColumn(state = listState) {
// ...
}
if (listState.layoutInfo.visibleItemsInfo.size > 0) {
println("無限に呼ばれる")
}
それを避けるためにコンポジションのタイミングだけ呼ぶようにするため、LaunchedEffectコンポーザブル関数を使います。
LaunchedEffect(listState) {
・・・
}
スクロールの変更を監視
snapshotFlow()
このままだとコンポジションされたときの一度しか呼ばれないので、snapshotFlow()を利用します。
snapshotFlow()の仕組みは、引数のブロック内で利用しているState(この場合は、listState.layoutInfo)が変更されたときに、再度引数のblockを呼び出すようになっています。
LaunchedEffect(listState) {
snapshotFlow {
println("call snapshotFlow block")
listState.layoutInfo
}.collect {
}
}
この状態でスクロールすると「call snapshotFlow block」が出力されます。(listState.layoutInfoを呼び出さないとsnapshotFlowのblockは呼び出されません)
スクロール条件によってログを送信する
以前の記事に書いたlistState.layoutInfoやlistState.layoutInfo.visibleItemsInfo[index]を使って、アイテムが7割表示されたら、ログを送信するロジックです。
ポイントは、distinctUntilChanged()で同じ状態では送らないようにして、filter()によってログ送信する条件を指定しています。
LaunchedEffect(listState) {
snapshotFlow {
val itemHeight = listState.layoutInfo.visibleItemsInfo.last().size
val itemTop = listState.layoutInfo.visibleItemsInfo.last().offset
val deviceHeight = listState.layoutInfo.viewportSize.height
val viewableTopPixel = deviceHeight - itemTop
val isTarget = viewableTopPixel > itemHeight * 0.7
Pair(isTarget, listState.layoutInfo.visibleItemsInfo.last().index)
}.distinctUntilChanged()
.filter { it.first }
.collect {
// ログ送信
Analytics().sendViewableLog(it.second)
}
}
ソーシャル経済メディア NewsPicks メンバーの発信を集約しています。公式テックブログはこちら→ tech.uzabase.com/archive/category/NewsPicks
Discussion