Jetpack ComposeでTopAppBarとStatus Barの色をスクロールに合わせて変化させたい
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)@Composablefun 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.gradleのdependenciesには、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()で取得したtopAppBarStateをpinnedScrollBehavior()に渡して、scrollBehaviorを取得します。
TopAppBarとスクロールコンテンツ(上の例ではLazyColumn)は、Scaffoldを使ってレイアウトします。作成したscrollBehaviorオブジェクトをScaffold内のTopAppBarのscrollBehaviorにセットし、Scaffold自体のmodifierにscrollBehavior.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.setDecorFitsSystemWindowsにfalseを指定します。これは基本的にはActivityのonCreateでsetContentを呼び出す前に設定します。
この設定はデフォルトではtrueになっており、この状態だとSystemBar (StatusBar + NavigationBar) の領域にはアプリのコンテンツが描画されません。そのままだと、TopAppBarがStatusBarの後ろに回り込んでくれないので、スクロールによってTopAppBarの色が変化してもStatusBarの部分は白いまま(ライトモードの場合)になってしまいます。

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

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