無限スクロールを作ってPagingを学んだ話

2026年4月7日

Androidエンジニアでインターンをしている際にPagingに触れる機会があったため、そこで学習したことをまとめます。

Pagingとは?

簡単に言えば、分割してデータを都度取得する という処理です。

画面上にリストを表示させる場合、データはサーバーから取得する必要がありますが、これを一度に行おうとすると処理が重くなり初期の描画速度に悪影響を与えます。

そこで、分割して取得することでユーザー体験を良くしようという目的で行われる処理になります。

Android公式ライブラリ Paging 3

RecyclerViewの最後尾の要素までスクロールすると自動で新しい要素を更新するというライブラリです。データソースと取得方法だけ指定すれば、あとはライブラリ側でよしなにやってくれるので便利なライブラリです。

上下スクロールのページングに加えて、読み込み中などの状態を簡単に扱えたり、リストの要素の重複の対応もされているなど便利な機能を揃えたライブラリになっています。

ページング ライブラリの概要  |  App architecture  |  Android Developers
Android Developers

自前で実装してみた

こんな感じ ↓

仕組みはシンプルで、LazyListState を用いてスクロール位置を計算し、derivedStateOfshopshotFlow でバッファの計算を行ってページングを発火するというものです。

今回は連番の数字を自動生成してページングするという非常に軽量な処理のため動作に問題は起こりませんでしたが、実際のデータを読み込む場合には、読み込み中の対応も必要になるなどの手間が増えると思われます。

また、下スクロールのページングしか対応していないため、リストの要素数に上限を設けて上スクロールのページングも自前実装に挑戦する予定です。
UIのズレなどが起こりそうで時間がかかりそうですが...

実装コード

Kotlin
@Composable
fun MainScreen(modifier: Modifier = Modifier) {
    val viewModel: MainViewModel = viewModel()
    val list = viewModel.uiList.collectAsState()
    val listState = rememberLazyListState()

    // buffer(=20件)よりも下回った場合にページング処理を行う
    val isRequiredMoreLoad = remember {
        derivedStateOf {
            val buffer = 20
            val lastVisibleIndex = listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0
            list.value.size - lastVisibleIndex <= buffer
        }
    }

    // ページングの発火点
    LaunchedEffect(isRequiredMoreLoad.value) {
        if (isRequiredMoreLoad.value) {
            viewModel.reload(30)
        }
    }

    LazyColumn() { ... }
}
Kotlin
class MainViewModel() : ViewModel() {
    private val _uiList = MutableStateFlow<List<Long>>((0L..30).toList())
    val uiList: StateFlow<List<Long>> = _uiList.asStateFlow()

    val useCase = MainUseCase()

    // ページング処理
    fun reload(limit: Int) {
        val addList = useCase.makeItems(_uiList.value, limit)
        _uiList.update {
            useCase.addItems(it, addList)
        }
    }
}
Kotlin
class MainUseCase() {
    // 追加する連番の配列を生成
    fun makeItems(list: List<Long>, limit: Int): List<Long> {
        val last = list.last()
        return (last..last + limit).toList()
    }

    // 配列を追加
    fun addItems(baseList: List<Long>, addList: List<Long>): List<Long> {
        return baseList.plus(addList)
    }
}

GitHub リポジトリ

GitHub - kyo1941/infinite-scroll-app: 無限スクロールの実験用app無限スクロールの実験用app. Contribute to kyo1941/infinite-scroll-app development by creating an account on GitHub.
GitHub