Jetpack Compose で追加読み込みに対応したLazyColumn
を実装しましたので、その方法をまとめます。
具体的には、一番下までスクロールしたときに追加読み込みを行う処理を呼び出すところまでの実装になります。
はじめに
まず、Jetpack Compose にはLazy コンポーザブルという Composable があり、RecyclerView と同様の役割を担います。
Lazy コンポーザブルにはLazyColumn
やLazyRow
などがあります。本記事ではLazyColumn
を使用します。
実装コード
以下に実装コードを示します(ご指摘いただきました@Nabe1216 さん、ありがとうございました)。
/**
* 追加読み込みに対応したリスト
* @param items 表示したいアイテム
* @param onAppearLastItem リストの一番最後に到達した際に呼ばれる関数
*/
@Composable
fun AdditionalReadableItems(items: List<String>, onAppearLastItem: (Int) -> Unit) {
val listState = rememberLazyListState().apply {
OnAppearLastItem(onAppearLastItem = onAppearLastItem)
}
LazyColumn(state = listState) {
items(items) {
Text(text = it)
Divider(color = Color.Gray)
}
}
}
/**
* リストの一番最後に到達した際、追加読み込みなどを行うための処理を発火する
* @param onAppearLastItem リストの一番最後に到達した際に呼ばれる関数
*/
@Composable
fun LazyListState.OnAppearLastItem(onAppearLastItem: (Int) -> Unit) {
val isReachedToListEnd by remember {
derivedStateOf {
// 追加読み込みを行う条件
// 1. アイテム総数が画面に収まりきらないほどある
// 2. リストの下端までスクロールされた
layoutInfo.visibleItemsInfo.size < layoutInfo.totalItemsCount &&
layoutInfo.visibleItemsInfo.lastOrNull()?.index == layoutInfo.totalItemsCount - 1
}
}
LaunchedEffect(Unit) {
snapshotFlow { isReachedToListEnd }
.filter { it }
.collect {
onAppearLastItem(layoutInfo.totalItemsCount)
}
}
}
関数ごとにそれぞれ解説します。
解説
AdditionalReadableItems
(UI)部分
まず、rememberLazyListState()
でリストの状態を取得しておきます。
OnAppearLastItem(onAppearLastItem = onAppearLastItem)
については後で説明します。
val listState = rememberLazyListState().apply {
OnAppearLastItem(onAppearLastItem = onAppearLastItem)
}
次に、リスト部分の UI を作成します。
本記事ではここはあまり重要でないため、簡易的にしています。
LazyColumn(state = listState) {
items(items) {
Text(text = it)
Divider(color = Color.Gray)
}
...
}
ここで、state = listState
としておくことでリストの状態がリアルタイムで反映されるようになります。
LazyListState.OnAppearLastItem
(ロジック)部分
まず、リストの一番最後に到達したかどうかを判定する処理を作成します。
処理についてはLazyListState
の拡張関数として作成することで、他の Composable でも使いまわせるようにしておくと便利です。
val isReachedToListEnd by remember {
derivedStateOf {
// 追加読み込みを行う条件
// 1. アイテム総数が画面に収まりきらないほどある
// 2. リストの下端までスクロールされた
layoutInfo.visibleItemsInfo.size < layoutInfo.totalItemsCount &&
layoutInfo.visibleItemsInfo.lastOrNull()?.index == layoutInfo.totalItemsCount - 1
}
}
listState.layoutInfo.visibleItemsInfo.size
は現在表示中のアイテムの数です。listState.layoutInfo.totalItemsCount
はアイテムの総数です。
現在表示中のアイテムの数がアイテムの総数より少ないか(アイテム総数が画面に収まりきらないほどあるか)をチェックすることで、何もしていないのに追加読み込みが行われてしまうのを防ぎます。
layoutInfo.visibleItemsInfo.lastOrNull()?.index
は現在表示中のアイテムの一番最後のインデックスです。これとアイテムの総数を比較し、リストの下端までスクロールされたかをチェックします。
また、derivedStateOf
を使用することで、状態が変化した時のみ計算されるようにして無駄に再コンポーズされないようにします。
次に、リストの一番最後に到達した時のみonAppearLastItem
を発火する処理を作成します。
LaunchedEffect(Unit) {
snapshotFlow { isReachedToListEnd }
.filter { it }
.collect {
onAppearLastItem(layoutInfo.totalItemsCount)
}
}
LaunchedEffect
は Composable が表示されるタイミングで何か処理を実行したい時に使用します。引数にUnit
のような定数を渡すことで、ライフサイクルと一致する作用を作成できます。
isReachedToListEnd
にはリストの一番最後に到達したかどうかが入っているので、それをコールド Flow に変換して流し、true
だったらonAppearLastItem
を実行します。
また、下記のエラーが出る場合はkotlinx.coroutines.flow.collect
が import されているか確認してください。
Type mismatch.
Required: FlowCollector<Boolean>
Found: () → Unit
これで追加読み込みに対応したLazyColumn
を実装できました。
まとめ
Jetpack Compose で追加読み込みに対応したLazyColumn
を実装する方法をまとめました。
Jetpack Compose は 2021 年 11 月 1 日時点ではまだ情報が少なく、複雑な実装をしようとすると手探りの状態になることが多いです。
同じような実装をしようとしていてうまくいかない人への助けとなれば幸いです。
参考URL
- android - Get last visible item index in jetpack compose LazyColumn - Stack Overflow
- Jetpack Composeでリストの最後を検知する - Qiita
- JetpackComposeの無限リスト/ページリスト
- Compose における副作用 | Jetpack Compose | Android Developers
- Jetpack ComposeのLaunchedEffectについて - Qiita
- android - Type mismatch inferred type is () -> Unit but FlowCollector was expected - Stack Overflow