Jetpack composeは、アプリ開発に必要な一通りのUIコンポーネントをライブラリで提供しています。
そのライブラリ中のTopAppBarについて、構成や使用方法などをまとめます。
※環境:Android Studio Koala | 2024.1.1
Kotlin 1.9.0
Compose Compiler 1.5.1
androidx.compose.material3:material3 1.1.1
UIの概要
TopAppBarはアプリ画面の上部に表示される帯状のUIです。
基本は、Scaffoldに組み込んで使用します。
※Scaffoldについては「Compose UI:Scaffold」を参照
Scaffold( topBar = { @OptIn(ExperimentalMaterial3Api::class) TopAppBar( colors = TopAppBarDefaults.topAppBarColors( containerColor = MaterialTheme.colorScheme.primaryContainer, titleContentColor = MaterialTheme.colorScheme.primary, ), title = { Text("Top app bar") }, navigationIcon = { IconButton(onClick = { /* クリック時の処理 */ }) { Icon( imageVector = Icons.Filled.ArrowBack, contentDescription = "Back" ) } }, actions = { IconButton(onClick = { /* クリック時の処理 */ }) { Icon( imageVector = Icons.Filled.Menu, contentDescription = "Menu" ) } } ) } ) { innerPadding -> Column( modifier = Modifier .padding(innerPadding) .verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.spacedBy(16.dp), ) { for(i in 0..15) { Text( modifier = Modifier.padding(start = 8.dp, end = 8.dp), text = "スクロールアイテム(${i})" ) } } }
TopAppBarはマテリアルデザインの指標に準拠しています。
帯の左端に「ナビケーション」、右端に「アクション」、真ん中に「タイトル」を配置するレイアウトが標準です。
※詳細は「マテリアルデザイン:Top app bars」を参照してください。
関数の引数
TopAppBarの状態(装飾、演出など)は、引数により指定できます。
@ExperimentalMaterial3Api @Composable fun TopAppBar( title: @Composable () -> Unit, modifier: Modifier = Modifier, navigationIcon: @Composable () -> Unit = {}, actions: @Composable RowScope.() -> Unit = {}, windowInsets: WindowInsets = TopAppBarDefaults.windowInsets, colors: TopAppBarColors = TopAppBarDefaults.topAppBarColors(), scrollBehavior: TopAppBarScrollBehavior? = null ) { ... }
引数 | 概要 | |
---|---|---|
title | @Composable () -> Unit | タイトルのスロット |
modifier | Modifier | UI全般のCompose修飾子 |
navigationIcon | @Composable () -> Unit | ナビゲーションのスロット |
actions | @Composable RowScope.() -> Unit | アクションのスロット |
windowInsets | WindowInsets | インセット(はめ込み位置) |
colors | TopAppBarColors | バーの配色 ・コンテナ(containerColor) ・タイトル(titleContentColor) ・ナビゲーションアイコン (navigationIconContentColor) ・アクションアイコン (actionIconContentColor) ・スクロール時のコンテナ (scrolledContainerColor) |
scrollBehavior | TopAppBarScrollBehavior? | NestedScrollから状態の通知 |
派生タイプ
TopAppBar関数をベースにした派生タイプが定義されています。
TopAppBarは「アノテーション:@ExperimentalMaterial3Api」付きです。
@ExperimentalMaterial3Apiは「実験的なAPI」を意味します。
ですので、今後のリリースで変更や削除の可能性があります。その点を理解した上で使用する必要があります。
使用に際して、上記の点についての許諾(アノテーション:@OptIn)が求められます。
(Small)TopAppBar(ベース)
タイトルが帯の右側に表示されます。高さはバー1つ分です。
TopAppBarは派生タイプのベースになる関数です。
CenterAlignedTopAppBar
タイトルが帯の中央に表示されます。高さはバー1つ分です。
MediumTopAppBar
ナビゲーション・アクションとタイトルが分かれて表示されます。高さはバー2つ分です。
タイトル欄が広く使える利点があります。しかし、タイトルのスロットに入るUIとしてText(Conpose UI)を想定おり、拡張性は低いです。
LargeTopAppBar
ナビゲーション・アクションとタイトルが分かれて表示されます。高さはバー2つ分です。タイトルが他の派生タイプよりも大きく表示されます。
タイトル欄が広く使える利点があります。しかし、タイトルのスロットに入るUIとしてText(Conpose UI)を想定おり、拡張性は低いです。
関数の構成
TopAppBar関数(派生タイプを含む)は内部でSingle/TwoRowTopAppBar関数を呼び出しています。
SingleRowTopAppBar1つのバーで構成されます。高さはバー1つ分です。
TwoRowTopAppBar上下に並ぶ2つのバーで構成されます。高さはバー2つ分です。上段はナビゲーションとアクション、下段はタイトルを表示します。
Single/TwoRowTopAppBar関数以下はinternal/private修飾子付きの関数やプロパティなので、一般のユーザは扱うことができません。
スクロールに連動した折り畳み
折り畳み動作
折り畳み動作のサンプル(LargeTopAppBarの例)です。
動作の様子が理解しやすいように、あえて、ゆっくりとリストをスワイプしています。
動作の様子は次のようになります。
上にスワイプ- (1)バーの高さが減る、リストはスクロールしない
- (2)バーの高さが最小になると、リストのスクロールが始まる
- (1)リストがスクロール、バーの高さは変わらない(最小のまま)
- (2)リストの先頭が表示されると、バーの高さが増え始める
@OptIn(ExperimentalMaterial3Api::class) @Composable fun LargeTopAppBarPanel() { // val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) // val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState()) val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState()) Scaffold( // ↑↑ (0)ScrollBehaviorの作成 ↑↑ modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { // ↑↑ (2)Conectionの指定 ↑↑ LargeTopAppBar( colors = TopAppBarDefaults.topAppBarColors( containerColor = MaterialTheme.colorScheme.primaryContainer, titleContentColor = MaterialTheme.colorScheme.primary, ), title = { Text("Top app bar") }, navigationIcon = { IconButton(onClick = { /* クリック時の処理 */ }) { Icon( imageVector = Icons.Filled.ArrowBack, contentDescription = "Back" ) } }, actions = { IconButton(onClick = { /* クリック時の処理 */ }) { Icon( imageVector = Icons.Filled.Menu, contentDescription = "Menu" ) } }, scrollBehavior = scrollBehavior // (3)状態(バーの高さの変化)を通知 ) } ) { innerPadding -> Column( modifier = Modifier .padding(innerPadding) .verticalScroll(rememberScrollState()), // (1)Dispatcherの指定 verticalArrangement = Arrangement.spacedBy(16.dp), ) { for(i in 0..15) { Text( modifier = Modifier.padding(start = 8.dp, end = 8.dp), text = "スクロールアイテム(${i})" ) } } } }
NestedScroll(ネストスクロール)
TopAppBarの折り畳みは、「子UI(Column)の検出したスクロール(スワイプ)が親UI(Scaffold)に伝搬して、親UIが何かの挙動をスクロールに連動して起こす」動作です。
このような機能をNestedScroll(ネストスクロール)といいます。
Jetpack Composeにおいて、各UI要素の状態(表示内容)は引数を介して上位から下位階層へ伝搬させます。その逆に、イベントは下位から上位階層へ伝搬させて処理します。これが基本的なデータの流れです。
スクロールはイベントですから、下位から上位階層へ伝搬させて処理することが基本です。
NestedScrollはこの基本的なデータの流れに従って機能を実現しています。
NestedScrollの機能を実現するために必要な要素は2つです。
Dispatcherスクロール(スワイプ)を検出して、イベントを変化量へ変換した後に、親UIへ送ります。
リストの作成を目的にしたLazyリストはDispatcherをデフォルトで含みます。また、Columun/RowはModifier.verticalScroll/horizontalScroll修飾子の指定で、Dispatcherが組み込まれます。
Connection子UIから受け取ったイベントの変化量をもとに、UIの新たな状態を作り出します。
TopAppBar用にScrollBehaviorが用意されています。この中に、ConnectionとUIの新たな状態に対するTopAppBarの振る舞い(折り畳みの様子)が定義されています。また、ScrollBehaviorは3つのタイプがあり、振る舞いが異なります。
ScrollBehaviorのタイプ
ScrollBehaviorのタイプの違いを示します。
pinnedScrollBehavior
enterAlwaysScrollBehavior
exitUntilCollapsedScrollBehavior
※折り畳み動作のサンプルで示した動画と同じ
スクロールに連動したバー色
NestedScrollが設定されている場合、スクロールに連動したバー色の変更が可能です。
scrolledContainerColorを指定
スクロール中は引数(scrolledContainerColor)に指定された色になります。
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) Scaffold( modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { TopAppBar( colors = TopAppBarDefaults.topAppBarColors( scrolledContainerColor = MaterialTheme.colorScheme.tertiaryContainer, containerColor = MaterialTheme.colorScheme.primaryContainer, titleContentColor = MaterialTheme.colorScheme.primary, ), title = { Text("Top app bar") }, navigationIcon = { ... }, actions = { ... }, scrollBehavior = scrollBehavior ) } ) { innerPadding -> ... }
コンテナ色がsurfaceの時
スクロール中はelevationに連動した影(透過する黒)が付きます。
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) Scaffold( modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { TopAppBar( colors = TopAppBarDefaults.topAppBarColors( containerColor = MaterialTheme.colorScheme.surface, titleContentColor = MaterialTheme.colorScheme.primary, ), title = { Text("Top app bar") }, navigationIcon = { ... }, actions = { ... }, scrollBehavior = scrollBehavior ) } ) { innerPadding -> ... }
影付きのバー
Viewシステムのアプリケーションバーは影が付いていました。しかし、Jetpack composeにおけるマテリアルデザインのアプリケーションバーは影が付きません。
必要であれば、Modifier修飾子を使って影を付けることが可能です。
TopAppBar( modifier = Modifier.shadow(elevation = 10.dp), colors = TopAppBarDefaults.topAppBarColors( containerColor = MaterialTheme.colorScheme.primaryContainer, titleContentColor = MaterialTheme.colorScheme.primary, ), title = { Text("Top app bar") }, navigationIcon = { ... }, actions = { ... }, scrollBehavior = scrollBehavior ) ...
インセットの働き
インセットはステータスバーとアプリケーションバーの重なりを避ける働きをします。
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { ScaffoldTheme { @OptIn(ExperimentalMaterial3Api::class) Scaffold( modifier = Modifier.fillMaxSize(), topBar = { TopAppBar( colors = TopAppBarDefaults.topAppBarColors( containerColor = MaterialTheme.colorScheme.primaryContainer, titleContentColor = MaterialTheme.colorScheme.primary, ), // windowInsets = TopAppBarDefaults.windowInsets, // デフォルト windowInsets = WindowInsets(0.dp, 0.dp, 0.dp, 0.dp), title = { Text("Top app bar") }, navigationIcon = { ... }, actions = { ... }, ) } ) { innerPadding -> ... } } } }
関連記事: