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

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

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

※以前このページではスクロールに合わせてStatus Barの色を変化させる方法を紹介していましたが、material3のAPIの変更により、その方法が使えなくなりました。そのため、透明にしたStatus Barの裏側にTopAppBarを回り込ませる方法に変更しました。

やりたいこと

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

サンプル

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

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

  • Kotlin 1.7210
  • Compose Compiler 1.3.2
  • Compose Libraries 1.2.1
  • Material3 1.0.0-rc01
  • Accompanist 0.25.1

ソースコードと結果

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun StatusBarColorOnScrollSample() {
    val systemUiController = rememberSystemUiController()
    val isDark = isSystemInDarkTheme()
    SideEffect {
        systemUiController.setStatusBarColor(
            color = Color.Transparent,
            darkIcons = !isDark,
        )
    }

    val topAppBarState = rememberTopAppBarState()
    val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(topAppBarState)
    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)
                )
            }
        }
    }
}
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        /* StatusBarやNavigationBarの裏側にも描画する */
        WindowCompat.setDecorFitsSystemWindows(window, false)

        setContent {
            ...
        }
    }
}

結果

説明

サンプルのようなStatusBarとTopAppBarの色の変化は、以下の3ステップで実現できます。

  • TopAppBarの色をスクロールに合わせて変化させる
  • StatusBarを透明にする
  • TopAppBarをStatusBarの下に回り込ませる

依存関係の追加

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を取得します。

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

StatusBarを透明にする

val systemUiController = rememberSystemUiController()
val isDark = isSystemInDarkTheme()
SideEffect {
    systemUiController.setStatusBarColor(
        color = Color.Transparent,
        darkIcons = !isDark,
    )
}

StatusBarの色は、AccompanistのSystem UI ControllerのsetStatusBarColor()を使います。ここでは透明にしたいので、color引数にColor.Transparentを指定しています。

darkIcons引数は、StatusBarのアイコンの色を黒にするかどうかを指定する引数です。isSystemInDarkModeで現在ダークモードを使用中かどうかを取得し、ダークモード中はfalseを指定して白いアイコンに、ライトモード中はtrueを指定して黒いアイコンに設定しています。

StatusBarの後ろにTopAppBarを回り込ませる

WindowCompat.setDecorFitsSystemWindows(window, false)

StatusBarの領域にアプリのコンテンツを描画するには、WindowCompat.setDecorFitsSystemWindowsfalseを指定します。これは基本的にはActivityのonCreatesetContentを呼び出す前に設定します。

この設定はデフォルトではtrueになっており、この状態だとSystemBar (StatusBar + NavigationBar) の領域にはアプリのコンテンツが描画されません。そのままだと、TopAppBarがStatusBarの後ろに回り込んでくれないので、スクロールによってTopAppBarの色が変化してもStatusBarの部分は白いまま(ライトモードの場合)になってしまいます。

WindowCompat.setDecorFitsSystemWindowsfalseを指定することにより、TopAppBarがStatusBarの領域まで広がり、StatusBarの部分もスクロールに合わせて色が変化するようになります。

なお、WindowCompat.setDecorFitsSystemWindowsfalseを指定した場合、コンテンツ(文字やアイコンなど)がStatusBarやNavigationBarに重ならないように、Paddingを適切に設定する必要があるのですが、material3のTopAppBarはライブラリ側でPaddingを設定してくれているので、アプリ側では特に何もする必要がありません。(material 3 v1.0.0-beta01以降)

JetpackCompose一問一答

Jetpack Compose一問一答

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

この記事をシェア