Jetpack ComposeでTopAppBarとStatus Barの色をスクロールに合わせて変化させたい

この記事をシェア
JetpackCompose一問一答

Material3のTopAppBarは、コンテンツのスクロールに合わせて色が変化します。今回はこれの実現方法を紹介します。さらに、StatusBarの色もTopAppBarに合わせて変化させる方法も説明します。

やりたいこと

Material3のTopAppBar(画面のタイトルなどが表示される部分。従来はToolBarやActionBarとも呼ばれていた。)は、コンテンツのスクロールに合わせて色を変化させるとガイドラインに定められています。一方でStatusBar(通知や時計などが表示される部分)の色はガイドラインには明記されていませんが、GmailなどのGoogle製アプリやAndroid標準の設定アプリなどでは、TopAppBarの色に合わせてStatusBarの色も変化しています。今回はこれを、TopAppBarScrollBehaviorとAccompanistのSystem UI Controllerを使って実現します。

サンプル

ソースコードはGitHubにあります。

主な環境は以下の通りです。

  • Kotlin 1.7.10
  • Compose Compiler 1.3.0
  • Compose Libraries 1.2.1
  • Material3 1.0.0 alpha15
  • Accompanist 0.25.1

ソースコードと結果

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun StatusBarColorOnScrollSample() {
    val topAppBarState = rememberTopAppBarState()
    val colorTransitionFraction by remember {
        derivedStateOf { topAppBarState.overlappedFraction }
    }
    val statusBarColor = TopAppBarDefaults.centerAlignedTopAppBarColors()
        .containerColor(colorTransitionFraction).value
    val systemUiController = rememberSystemUiController()
    val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(topAppBarState)

    SideEffect {
        systemUiController.setStatusBarColor(statusBarColor)
    }

    Scaffold(
        modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
        topBar = {
            CenterAlignedTopAppBar(
                title = { Text("TopAppBar") },
                scrollBehavior = scrollBehavior,
            )
        }
    ) { paddingValues ->
        LazyColumn(contentPadding = paddingValues) {
            items(100) { count ->
                Text(
                    text = "Item ${count + 1}",
                    modifier = Modifier.fillMaxWidth().height(30.dp).padding(20.dp, 4.dp)
                )
            }
        }
    }
}

結果

説明

依存関係の追加

dependencies {
    implementation 'androidx.compose.material3:material3:x.x.x'
    implementation 'com.google.accompanist:accompanist-systemuicontroller:x.x.x'
}

Material3に移行していることが前提になります。build.gradledependenciesには、material3ライブラリを追加します。Material3への移行について、詳しくは「Jetpack ComposeアプリをMaterial Design 3へ移行する」を参照してください。

また、AccompanistのSystem UI Controllerを使います。accompanist-systemuicontrollerを追加しておきます。System UI Controllerを使ってStatus Barの色を変化させる方法は、「Jetpack ComposeでStatus Barの色を変更したい」にも詳しく書いていますので、そちらも参考にしてください。

TopAppBarの色の変化

val topAppBarState = rememberTopAppBarState()
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(topAppBarState)
Scaffold(
    modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
    topBar = {
        CenterAlignedTopAppBar(
            title = { Text("TopAppBar") },
            scrollBehavior = scrollBehavior,
        )
    }
) { paddingValues ->
    LazyColumn( ... ) { ... }
}

スクロールに合わせてTopAppBarの色を変化させるには、androidx.compose.material3パッケージのTopAppBarScrollBehaviorを使います。

TopAppBarScrollBehaviorには、コンテンツをスクロールした時のTopAppBarの振る舞いが実装されていて、TopAppBarDefaultsに3種類(pinnedScrollBehavior, enterAlwaysScrollBehavior, exitUntilCollapsedScrollBehavior)用意されています。最も基本的なpinnedScrollBehaviorは、TopAppBarの位置やサイズは固定で、色のみ変化させます。

TopAppBarScrollBehaviorオブジェクトを取得するには、TopAppBarStateオブジェクトが必要です。rememberTopAppBarState()で取得したtopAppBarStatepinnedScrollBehavior()に渡して、scrollBehaviorを取得します。topAppBarStateオブジェクトは、このあとのStatus Barの色の設定にも使います。

TopAppBarとスクロールコンテンツ(上の例ではLazyColumn)は、Scaffoldを使ってレイアウトします。作成したscrollBehaviorオブジェクトをScaffold内のTopAppBarscrollBehaviorにセットし、Scaffold自体のmodifierscrollBehavior.nestedScrollConnectionを渡すことによって、コンテンツのスクロール状態とTopAppBarの状態が同期します。

StatusBarの色の変化

val topAppBarState = rememberTopAppBarState()
val colorTransitionFraction by remember {
    derivedStateOf { topAppBarState.overlappedFraction }
}
val statusBarColor = TopAppBarDefaults.centerAlignedTopAppBarColors()
    .containerColor(colorTransitionFraction).value
val systemUiController = rememberSystemUiController()

SideEffect {
    systemUiController.setStatusBarColor(statusBarColor)
}

StatusBarの色は、AccompanistのSystem UI Controllerを使って設定します。先ほど作成したtopAppBarStateオブジェクトを使って、TopAppBarの現在の色を取得し、setStatusBarColor()に渡します。

TopAppBarの色は、TopAppBarDefaultsから取得します。この時、色の変化の割合を指定して、現在のスクロール状態に合わせた色を取得します。色の変化はTopAppBarState.overlappedFractionと連動していますが、そのまま使うと画面をスクロールするたびに再コンポジションが多発してしまうので、derivedStateOfを使って、overlappedFractionが変化した時だけ再コンポジションが走るようにします。

JetpackCompose一問一答

Jetpack Compose一問一答

コンテンツは随時追加していきます。

この記事をシェア