Jetpack composeは、アプリ開発に必要な一通りのUIコンポーネントをライブラリで提供しています。
そのライブラリ中のScaffoldについて、構成や使用方法などをまとめます。
※環境:Android Studio Koala | 2024.1.1
Kotlin 1.9.0
Compose Compiler 1.5.1
androidx.compose.material3:material3 1.1.1
目次
UIの概要
Scaffoldはアプリケーション画面のテンプレートです。
「トップバー、ボトムバー、アクションボタン、スナックバー、コンテンツ」といった5つの表示要素を、下図のように配置します。
この画面構成は「Material Designe」の指標に準拠しています。
アプリの見た目と操作性を共通化して、ユーザエクスペリエンス(使い心地)を向上させる目的を持ちます。
関数の引数
Scaffoldの状態(装飾、演出など)は、引数により指定できます。
@Composable fun Scaffold( modifier: Modifier = Modifier, topBar: @Composable () -> Unit = {}, bottomBar: @Composable () -> Unit = {}, snackbarHost: @Composable () -> Unit = {}, floatingActionButton: @Composable () -> Unit = {}, floatingActionButtonPosition: FabPosition = FabPosition.End, containerColor: Color = MaterialTheme.colorScheme.background, contentColor: Color = contentColorFor(containerColor), contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets, content: @Composable (PaddingValues) -> Unit ) { ... }
引数 | 概要 | |
---|---|---|
modifier | Modifier | UI全般のCompose修飾子 |
topBar | @Composable () -> Unit | トップバーのスロット |
bottomBar | @Composable () -> Unit | ボトムバーのスロット |
snackbarHost | @Composable () -> Unit | スナックバーのスロット |
floatingActionButton | @Composable () -> Unit | FABのスロット |
floatingActionButtonPosition | FabPosition | FABの位置 End:右端 Center:中央 |
containerColor | Color | コンテンツの背景色 |
contentColor | Color | コンテンツの主色 |
contentWindowInsets | WindowInsets | バー部分のサイズ情報 |
content | @Composable (PaddingValues) -> Unit | コンテンツのスロット |
※スロット:UIの表示を司るコマンドブロック(Composable関数)を入力する引数 |
スロットとは「UIの表示を司るコマンドブロック(Composable関数)を代入する引数」のことです。Scaffoldは「5つのスロットを持つ関数」になります。
関数の構成
ScaffoldはSurfaceでパネル(バックグラウンド)を作成した上に、トップバー、ボトムバー、アクションボタン、スナックバー、コンテンツを配置する構成になっています。
ScaffoldLayout関数(Composable関数)は、描画処理のレイアウトフェーズで呼び出され、上記5つの表示要素の配置を決める処理を、システムへ登録します。
※描画処理については「Jetpack Compose:Composeによるアプリ画面の描画」を参照
@Composable fun Scaffold( modifier: Modifier = Modifier, ... containerColor: Color = MaterialTheme.colorScheme.background, contentColor: Color = contentColorFor(containerColor), ... ) { Surface(modifier = modifier, color = containerColor, contentColor = contentColor) { ScaffoldLayout( fabPosition = floatingActionButtonPosition, topBar = topBar, bottomBar = bottomBar, content = content, snackbar = snackbarHost, contentWindowInsets = contentWindowInsets, fab = floatingActionButton ) } }
ScaffoldはSurfaceを含みます。ですので、Android Studio(New Project)の作成するプロジェクトにおいて、Surface階層は不要です。
最新版のAndroid Studioは、デフォルトでScaffoldを使用する記述が出力されます。
class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { AppTheme { Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> /* ** コンテンツ */ } } } } }
class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { AppTheme { // Surface( // modifier = Modifier.fillMaxSize(), // color = MaterialTheme.colorScheme.background // ) { Scaffold(...) { ... } // } } } } }
トップバーのスロット(topBar)
スロットへトップバーのUIを記述します。
以下はBoxとTextでトップバーを自作した例です。
Scaffold( ... topBar = { Box( modifier = Modifier .fillMaxWidth() .height(60.dp) .background(color = MaterialTheme.colorScheme.primaryContainer), contentAlignment = Alignment.Center ) { Text(text = "Top bar") } }, ... ) { ... }
自作も可能ですが、「Material Designe」の指標に準拠したTopAppBar(Composable関数)がAPIに準備されています。こちらを使った方が容易です。
※TopAppBarについては「Compose UI:TopAppBar」を参照
ボトムバーのスロット(bottomBar)
スロットへボトムバーのUIを記述します。
以下はBoxとTextでボトムバーを自作した例です。
Scaffold( ... bottomBar = { Box( modifier = Modifier .fillMaxWidth() .height(60.dp) .background(color = MaterialTheme.colorScheme.primaryContainer), contentAlignment = Alignment.Center ) { Text(text = "Bottom bar") } }, ... ) { ... }
自作も可能ですが、「Material Designe」の指標に準拠したBottomAppBar(Composable関数)がAPIに準備されています。こちらを使った方が容易です。
※BottomAppBarについては「Compose UI:BottomAppBar」を参照
アクションボタンのスロット(floatingActionButton)
スロットへアクションボタンのUIを記述します。
以下はButtonでアクションボタンを自作した例です。
var presses by remember { mutableIntStateOf(0) } Scaffold( ... floatingActionButton = { Button( onClick = { presses++ scope.launch { // スナックバー呼出し snackbarHostState.showSnackbar("Snackbar") } } ) { Text(text = "Press") } }, ... ) { ... }
自作も可能ですが、「Material Designe」の指標に準拠したFloatingActionButton(Composable関数)がAPIに準備されています。こちらを使った方が容易です。
※FloatingActionButtonについては「Compose UI:FloatingActionButton」を参照
スナックバーのスロット(snackbarHost)
スロットへSnackbarHostを記述します。これはScaffoldへスナックバーを組み込む場合の定型です。
Scaffaoldへ組み込まれたスナックバーは、コンテンツの上、かつ、トップバー・ボトムバー・アクションボタンに重ならない位置へ表示するように、制御されます。
@Composable fun SnackbarHost( hostState: SnackbarHostState, // 表示の状態管理/起動ポイント(showSnackbar)の設置 modifier: Modifier = Modifier, snackbar: @Composable (SnackbarData) -> Unit = { Snackbar(it) } // 形状の定義(スロット) ) { ... }
さらに、SnackbarHostのsnackbarスロットへバー本体のUIを記述(形状の定義)します。2重のスロットになっています。
以下はBoxとTextでスナックバーを自作した例です。
val scope = rememberCoroutineScope() val snackbarHostState = remember { SnackbarHostState() } Scaffold( ... snackbarHost = { SnackbarHost(hostState = snackbarHostState) { Box( modifier = Modifier .fillMaxWidth() .height(40.dp) .padding(horizontal = 10.dp) .background(color = MaterialTheme.colorScheme.errorContainer), contentAlignment = Alignment.Center ) { Text( text = it.visuals.message, color = MaterialTheme.colorScheme.error ) } } }, ... ) { ... }
自作も可能ですが、「Material Designe」の指標に準拠したSnackbar(Composable関数)がAPIに準備されています。こちらを使った方が容易です。
※Snackbarについては「Compose UI:Snackbar」を参照
コンテンツのスロット( content )
スロットへコンテンツのUIを記述します。
以下はTextのみで構成されたコンテンツの例です。
var presses by remember { mutableIntStateOf(0) } Scaffold( ... ) { innerPadding -> Column( modifier = Modifier.padding(innerPadding), verticalArrangement = Arrangement.spacedBy(16.dp), ) { Text( modifier = Modifier.padding(8.dp), text = """ You have pressed the button $presses times. ${DummyStr('#', 500)} // ダミー:'#'が500文字 """.trimIndent(), ) } }
innerPaddingの働き
コンテンツのスロットは引数にinnerPaddingを持ちます。
以下は、このinnerPaddingを使用しない例です。
var presses by remember { mutableIntStateOf(0) } Scaffold( ... ) { innerPadding -> Column( // modifier = Modifier.padding(innerPadding), verticalArrangement = Arrangement.spacedBy(16.dp), ) { Text( modifier = Modifier.padding(8.dp), text = """ You have pressed the button $presses times. ${DummyStr('#', 500)} // ダミー:'#'が500文字 """.trimIndent(), ) } }
このサンプルからわかるように、innerPaddingはコンテンツの上下にスペースを設けて、トップバーならびにボトムバーとコンテンツの重なりを避ける働きをしています。
また、台紙に写真を貼る(挿入する)場合に設ける上下左右の余白をインセットと言います。
ですので、innerPaddingはスクリーンへコンテンツを貼る場合のインセットです。
画面のプレビュー(@Preview)
Scaffoldを使用したアプリケーション画面のプレビューを行うと、画面の下部に空欄が出来てしまいます。
これは、innerPaddingを使うことでインセットが指定された結果です。
この空欄はナビゲーションバーが入ります。プレビューはナビゲーションバーを表示できないため、空欄になってしまうようです。
※ナビゲーションバーについては「Android EmulatorでNavigation Barを表示」を参照
@Preview(showBackground = true, showSystemUi = true) @Composable private fun ScaffoldPreview() { AppTheme { ScaffoldExample() // Scaffoldを使用したTop階層 } }
![ナビゲーションキーのない端末でプレビュー](https://android.suzu-sd.com/wp-content/uploads/2024/04/PreviewScaffold_NavKeyX.png)
![ナビゲーションキーのない端末でエミュレーション](https://android.suzu-sd.com/wp-content/uploads/2024/04/EmulationScaffold_NavKeyX.png)
ナビゲーションキーのある端末(ハードのボタンを持つ端末)をプレビュー対象のデバイスに指定すれば、空欄は出来ません。
※プレビューについては 「Jetpack Compose:画面のプレビュー(@Preview)」を参照
: hw.mainKeys=yes # yes:HWのナビキーあり / no:HWのナビキーなし :
@Preview(showBackground = true, showSystemUi = true, device = "id:NavKeyO") @Composable private fun ScaffoldPreview() { AppTheme { ScaffoldExample() // Scaffoldを使用したTop階層 } }
![ナビゲーションキーのある端末でプレビュー](https://android.suzu-sd.com/wp-content/uploads/2024/04/PreviewScaffold_NavKeyO.png)
![ナビゲーションキーのある端末でエミュレーション](https://android.suzu-sd.com/wp-content/uploads/2024/04/EmulationScaffold_NavKeO.png)
関連記事: