🤸

Jetpack Compose で追加読み込みに対応した LazyColumn を実装する

2021/11/01に公開

Jetpack Compose で追加読み込みに対応したLazyColumnを実装しましたので、その方法をまとめます。

具体的には、一番下までスクロールしたときに追加読み込みを行う処理を呼び出すところまでの実装になります。

はじめに

まず、Jetpack Compose にはLazy コンポーザブルという Composable があり、RecyclerView と同様の役割を担います。

Lazy コンポーザブルにはLazyColumnLazyRowなどがあります。本記事ではLazyColumnを使用します。

実装コード

以下に実装コードを示します。


/**
 * 追加読み込みに対応したリスト
 * @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

Discussion