PagerのcurrentPage, settledPage, targetPageの挙動

この記事をシェア

Jetpack Compose 1.4.0 (本記事執筆時点ではalpha05)で、HorizontalPagerVerticalPagerが追加されました。これまでAccompanistで提供されていたものが、本家に取り込まれた形です。現時点ではまだExperimentalですが、今後は徐々に使われていくようになると思われます。

さて、これらのPagerの状態管理を担うPagerStateクラスには、currentPagesettledPagetargetPageというページ番号を表すプロパティが3つあります。currentPageはAccompanistにも存在しましたが、その他の2つはJetpack Composeで追加されました。これらのプロパティがどんな挙動をするのか、リファレンスを読んだだけではイメージがつかめなかったので、実際にサンプルを動かして試してみました。

サンプル

サンプルコードは下記のとおりです。映像はこれらの値を確認しやすいように、スロー再生にしています。

HorizontalPagerの上にcurrentPagesettledPagetargetPageを表示しています。ページが切り替わると値が変化しますが、変化するタイミングや値がそれぞれ異なることが分かります。

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun PagerSample() {
    Column {
        val pagerState = rememberPagerState()
        Text(text = "currentPage = ${pagerState.currentPage}")
        Text(text = "settledPage = ${pagerState.settledPage}")
        Text(text = "targetPage = ${pagerState.targetPage}")
        HorizontalPager(
            pageCount = 3,
            state = pagerState,
        ) { page ->
            Text(
                text = "Page $page",
                style = MaterialTheme.typography.displayLarge,
                modifier = Modifier
                    .fillMaxSize()
                    .border(width = 2.dp, color = MaterialTheme.colorScheme.onPrimaryContainer)
            )
        }
    }
}

currentPage

currentPageは、画面の中心に現在表示されているページです。ページ遷移中は、画面のちょうど半分までスクロールした時にcurrentPageの値が切り替わります。

APIリファレンスには下記のように書かれています。

The page that sits closest to the snapped position. This is an observable value and will change as the pager scrolls either by gesture or animation.(スナップされた位置に最も近いページ。これは観測可能な値であり、ジェスチャーまたはアニメーションによってページャーがスクロールするにつれて変化します。※DeepL訳)

https://developer.android.com/reference/kotlin/androidx/compose/foundation/pager/PagerState#currentPage()

currentPageはスクロール中も位置に合わせて値が切り替わるので、ページ遷移の効果を強調させるような用途で使うのに向いているプロパティと言えそうです。次の例は、currentPageを水色にしています。このような実装をすると、ページが切り替わったことを強調し、新しいページに目線を向けさせる効果が期待できそうです。

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun PagerSample() {
    Column {
        val pagerState = rememberPagerState()
        HorizontalPager(
            pageCount = 3,
            state = pagerState,
        ) { page ->
            val bgColor = if (page == pagerState.currentPage) Color.Cyan else Color.White
            Text(
                text = "Page $page",
                style = MaterialTheme.typography.displayLarge,
                modifier = Modifier
                    .fillMaxSize()
                    .background(bgColor)
            )
        }
    }
}

settledPage

settledPageも現在表示中のページを示しますが、こちらはスクロール中には値は更新されず、スクロール完了して画面が静止してから値が更新されます。”settled”という単語が耳慣れないですが、「確定した」などの意味を持っています。

APIリファレンスには下記のように書かれています。

The page that is currently “settled”. This is an animation/gesture unaware page in the sense that it will not be updated while the pages are being scrolled, but rather when the animation/scroll settles.(現在「確定」しているページです。これは、ページをスクロールしている間は更新されず、アニメーションやスクロールが落ち着いたときに更新されるという意味で、アニメーションやジェスチャーを意識していないページと言えます。※DeepL訳を一部改)

https://developer.android.com/reference/kotlin/androidx/compose/foundation/pager/PagerState#settledPage()

スクロールが完了したタイミングで値が変わるので、動画やアニメーションなどの動的なコンテンツを、スクロールが止まったタイミングでスタートする、といった用途に使えそうです。次の例では画面の文字をアニメーションさせています。最初に表示されているページのアニメーションは、そのページが見えなくなるまで継続しています。そして、スクロールが止まったタイミングで新しいページのアニメーションが開始しています。

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun PagerSample() {
    Column {
        val pagerState = rememberPagerState()
        HorizontalPager(
            pageCount = 3,
            state = pagerState,
            pageSpacing = 10.dp,
            modifier = Modifier.background(Color.LightGray)
        ) { page ->
            Box(
                contentAlignment = Alignment.Center,
                modifier = Modifier
                    .fillMaxSize()
                    .background(Color.White)
            ) {
                if (page == pagerState.settledPage) {
                    val infiniteTransition = rememberInfiniteTransition()
                    val rotation by infiniteTransition.animateFloat(
                        initialValue = 0f,
                        targetValue = 360f,
                        animationSpec = infiniteRepeatable(tween(3000, easing = LinearEasing))
                    )
                    Text(
                        text = "Page$page Playing!",
                        style = MaterialTheme.typography.displayLarge,
                        modifier = Modifier.graphicsLayer { rotationZ = rotation }
                    )
                } else {
                    Text(
                        text = "Page$page Stopped",
                        style = MaterialTheme.typography.displayLarge,
                    )
                }
            }
        }
    }
}

targetPage

targetPageは、スクロール中に意味を持つプロパティです。現在進行中のスクロールアニメーションが完了した時、どのページが表示された状態で止まるかを示します。最初の動画を見ていただくと分かりますが、必ずしも隣のページを示しているとは限りません。スワイプジェスチャーの速度などの条件によっては、2つ隣のページを示す場合もあり、スクロール中にも値が変わります。

APIリファレンスには下記のように書かれています。

The page this Pager intends to settle to. During fling or animated scroll (from animateScrollToPage this will represent the page this pager intends to settle to. When no scroll is ongoing, this will be equal to currentPage.(このページャーが移動しようとするページ。フライングまたはアニメーションスクロールの間(animateScrollToPageから)、これはこのページャーが落ち着く予定のページを表します。スクロールが進行していないとき、これはcurrentPageと同じになります。※DeepL訳)

https://developer.android.com/reference/kotlin/androidx/compose/foundation/pager/PagerState#targetPage()

targetPageは、ちょっと具体的な使い道が思いつきません。もしかすると、これから表示される予定のページのデータのロードを前もって開始する、などの目的で使えるかもなーと思ったりしましたが、スクロール中も値がころころ切り替わるので実装は複雑になりそうです。


以上、Jetpack Compose本家のPagerのcurrentPagesettledPagetargetPageについてでした。まだExperimeltalということで今後変わる可能性もある点はご留意ください。

この記事をシェア