Jetpack composeは、アプリ開発に必要な一通りのUIコンポーネントをライブラリで提供しています。
そのライブラリ中のSnackbarについて、構成や使用方法などをまとめます。
※環境:Android Studio Koala | 2024.1.1
Kotlin 1.9.0
Compose Compiler 1.5.1
androidx.compose.material3:material3 1.1.1
UIの概要
Snackbarは、ユーザによる操作の応答や、その時点のアプリの状態を、バー状のポップアップウィンドウで通知するUIです。
Scaffoldに組み込んで使用すると、アプリバーを避けて配置してくれるので便利です。もちろん、単体でも使用できます。
Snackbarはマテリアルデザインの指標に準拠しています。
※詳細は「マテリアルデザイン:Snackbar」を参照してください。
UIの実装
Snackbarを実装するには、4つの要素が必要です。
- (1)スナックバーの状態と起動ポイントを作成(SnackbarHostState)
- (2)スナックバーの配置を制御(SnackbarHost)
- (3)スナックバーの本体を定義(Snackbar)
- (4)スナックバーの起動(表示)
Box(modifier = Modifier.fillMaxSize()) { val _scope = rememberCoroutineScope() val _hostState = remember { SnackbarHostState() } // ↑↑ (1)状態と起動ポイントを作成 ↑↑ Text( modifier = Modifier.padding(8.dp), text = "${DummyStr('#', 600)}" // ダミー:'#'が600文字 ) Button( onClick = { _scope.launch { // ↓↓ (4)起動(表示)↓↓ _hostState.showSnackbar(message = "Snackbar !") } }, modifier = Modifier .align(alignment = Alignment.TopEnd) .padding(10.dp) ) { Text(text = "Push") } SnackbarHost( // (2)配置を制御 hostState = _hostState, modifier = Modifier.align(Alignment.BottomCenter) ){ Snackbar( // (3)本体を定義(タイプ1) snackbarData = it, containerColor = MaterialTheme.colorScheme.errorContainer, contentColor = MaterialTheme.colorScheme.onErrorContainer ) } }
var presses by remember { mutableIntStateOf(0) } val scope = rememberCoroutineScope() val snackbarHostState = remember { SnackbarHostState() } // ↑↑ (1)状態と起動ポイントを作成 ↑↑ @OptIn(ExperimentalMaterial3Api::class) Scaffold( modifier = Modifier.fillMaxSize(), topBar = { ... }, bottomBar = { ... }, floatingActionButton = { FloatingActionButton( onClick = { presses++ scope.launch { // ↓↓ (4)起動(表示)↓↓ snackbarHostState.showSnackbar("Snackbar") } }, ... ) { ... } }, snackbarHost = { SnackbarHost(hostState = snackbarHostState) { // (2)配置を制御 Snackbar( // (3)本体を定義(タイプ1) snackbarData = it, containerColor = MaterialTheme.colorScheme.errorContainer, contentColor = MaterialTheme.colorScheme.onErrorContainer ) } } ) { innerPadding -> Column( ... ) { Text( modifier = Modifier.padding(8.dp), text = """ You have pressed the button $presses times. ${DummyStr('#', 500)} // ダミー:'#'が500文字 """.trimIndent(), ) } }
また、SnackbarHostStateインスタン内に起動ポイント(showSnackbar関数)が作成されます。
(2)スナックバーの配置を制御(SnackbarHost) SnackbarHostの表示を指定した位置に、バーは配置されます。 (3)スナックバーの本体を定義(Snackbar) スナックバーの本体のレイアウトを定義します。 (4)スナックバーの起動(表示) 起動ポイント(showSnackbar関数)を実行することで、スナックバーは表示されます。起動ポイントの実行はコルーチンを用いた非同期で行います。そのため、起動ポイントはSuspend関数です。
関数の引数
Snackbarの状態(装飾、演出など)は、引数により指定できます。
SnackbarHost
@Composable fun SnackbarHost( hostState: SnackbarHostState, modifier: Modifier = Modifier, snackbar: @Composable (SnackbarData) -> Unit = { Snackbar(it) } ) { ... }
引数 | 概要 | |
---|---|---|
hostState | SnackbarHostState | HostStateのインスタンス |
modifier | Modifier | UI全般のCompose修飾子 |
snackbar | @Composable (SnackbarData) -> Unit | スナックバーのスロット デフォルト:Snackbar(it)(タイプ1) ※itはSnackbarDataのインスタンス |
Snackbar
タイプ1とタイプ2があります。※詳細は後述の「カスタムSnackbar」を参照
@Composable fun Snackbar( snackbarData: SnackbarData, modifier: Modifier = Modifier, actionOnNewLine: Boolean = false, shape: Shape = SnackbarDefaults.shape, containerColor: Color = SnackbarDefaults.color, contentColor: Color = SnackbarDefaults.contentColor, actionColor: Color = SnackbarDefaults.actionColor, actionContentColor: Color = SnackbarDefaults.actionContentColor, dismissActionContentColor: Color = SnackbarDefaults.dismissActionContentColor, ) { ... }
@Composable fun Snackbar( modifier: Modifier = Modifier, action: @Composable (() -> Unit)? = null, dismissAction: @Composable (() -> Unit)? = null, actionOnNewLine: Boolean = false, shape: Shape = SnackbarDefaults.shape, containerColor: Color = SnackbarDefaults.color, contentColor: Color = SnackbarDefaults.contentColor, actionContentColor: Color = SnackbarDefaults.actionContentColor, dismissActionContentColor: Color = SnackbarDefaults.dismissActionContentColor, content: @Composable () -> Unit ) { ... }
引数 | 概要 | |
---|---|---|
snackbarData(タイプ1) | SnackbarData | SnackbarDataのインスタンス 起動時の情報(showSnackbarの引数) |
modifier | Modifier | UI全般のCompose修飾子 |
action(タイプ2) | @Composable (() -> Unit)? | Actionのスロット |
dismissAction(タイプ2) | @Composable (() -> Unit)? | DismissActionのスロット |
actionOnNewLine | Boolean | Actionを別の行に分離(2段構成) false:1段構成、true:2段構成 |
shape | Shape | 角の丸み |
containerColor | Color | バーの色 |
contentColor | Color | メッセージの色 |
actionContentColor | Color | Actionの色 |
dismissActionContentColor | Color | DismissActionの色 |
content(タイプ2) | @Composable () -> Unit | コンテンツのスロット |
SnackbarHostState#showSnackbar
suspend fun showSnackbar( message: String, actionLabel: String? = null, withDismissAction: Boolean = false, duration: SnackbarDuration = if (actionLabel == null) SnackbarDuration.Short else SnackbarDuration.Indefinite ): SnackbarResult = ...
引数 | 概要 | |
---|---|---|
message | String | 表示するメッセージ |
actionLabel | String | Actionの追加とラベル名 |
withDismissAction | Boolean | DismissActionの追加 |
duration | SnackbarDuration | 表示時間 SnackbarDuration.Short:5秒程度 ※デフォルト SnackbarDuration.Long:10秒程度 SnackbarDuration.Indefinite:自動で閉じない |
関数の構成
Snackbar関数はSurfaceの中にOneRowSnackbarもしくはNewLineButtonSnackbarを格納する構成になっています。
この両者はactionNewLine引数により切り替わります。
※詳細は後述の「2段構成のバー」を参照
バーの配置
Scaffoldに組み込んだ場合
SnackbarをScaffoldに組み込んだ場合は、次の条件を満たした位置にバーが配置されます。
- (1)アプリバーとフローティングアクションボタンに重ならない
- (2)コンテンツの最上位レイヤー
単体で用いた場合
Snackbarを単体で用いる場合は、SnackbarHost(Composable関数)の表示を指定した位置にバーは配置されます。
ですので、次にあげる状況に影響を受けます。
UI要素の並び
ColumnはButton⇒Text⇒SnackbarHostの縦並びにします。ですので、Textの下に配置されます。
Column(modifier = Modifier.fillMaxSize()) { val _scope = rememberCoroutineScope() val _hostState = remember { SnackbarHostState() } Button( onClick = { _scope.launch { _hostState.showSnackbar(message = "Snackbar !") } }, modifier = Modifier.padding(10.dp) ) { Text(text = "Push") } Text( modifier = Modifier.padding(8.dp), text = "${DummyStr('#', 100)}" ) SnackbarHost(hostState = _hostState) // タイプ1 }
格納するコンテナの位置
縦に並ぶ中央のBoxにSnackbarHostがあります。ですので、中央のBoxに配置されます。
Box(modifier = Modifier.fillMaxSize()) { val _scope = rememberCoroutineScope() val _hostState = remember { SnackbarHostState() } Column(modifier = Modifier.fillMaxSize()) { Box(modifier = Modifier.weight(1.0f).fillMaxSize()) Box(modifier = Modifier.weight(1.0f).fillMaxSize() .background(Color.LightGray) ) { SnackbarHost(hostState = _hostState) // タイプ1 } Box(modifier = Modifier.weight(1.0f).fillMaxSize()) } Button( onClick = { _scope.launch { _hostState.showSnackbar(message = "Snackbar !") } }, modifier = Modifier.padding(10.dp) ) { Text(text = "Push") } }
UI要素の重なり
Boxは下から赤Text⇒SnackbarHost⇒黄Textの順に重ねます。ですので、黄Textの下に配置されます。
Box(modifier = Modifier.fillMaxSize()) { val _scope = rememberCoroutineScope() val _hostState = remember { SnackbarHostState() } Text( modifier = Modifier.padding(8.dp).align(Alignment.Center), color = Color.Red, text = "${DummyStr('#', 84)}" // ダミー:'#'が 84文字 ) SnackbarHost( // タイプ1 hostState = _hostState, modifier = Modifier.align(Alignment.Center) ) Text( modifier = Modifier.padding(8.dp).align(Alignment.Center), color = Color.Yellow, text = "${DummyStr('#', 112)}" // ダミー:'#'が112文字 ) Button( onClick = { _scope.launch { _hostState.showSnackbar(message = "Snackbar !") } }, modifier = Modifier.padding(10.dp) ) { Text(text = "Push") } }
SnackbarHostのModifier.Align修飾子
Box内のUI要素はデフォルトで表示領域の左上に表示されます。しかし、Modifier.align修飾子で位置を指定できます。
SnackbarHostも同様です。
Alignment.Centerで表示領域の中央に、Alignment.BottomCenterで表示領域の下部に配置されます。
Box(modifier = Modifier.fillMaxSize()) { val _scope = rememberCoroutineScope() val _hostState = remember { SnackbarHostState() } SnackbarHost( // タイプ1 hostState = _hostState, // modifier = Modifier.align(Alignment.TopCenter), modifier = Modifier.align(Alignment.Center), // modifier = Modifier.align(Alignment.BottomCenter) ) Button( onClick = { _scope.launch { _hostState.showSnackbar(message = "Snackbar !") } }, modifier = Modifier.padding(10.dp) ) { Text(text = "Push") } }
バーの閉じ方
バーを閉じる方法は、自動と手動の2つがあります。
自動で閉じる
指定時間の経過後に自動で閉じます。
指定時間はSnackbarHostState#showSnackbar関数のduration引数で指定し、3つの時間長があります。
SnackbarDuration.Long
SnackbarDuration.Indefinite
:10秒程度
:自動で閉じない
val _scope = rememberCoroutineScope() val _hostState = remember { SnackbarHostState() } Button( onClick = { _scope.launch { _hostState.showSnackbar( message = "Snackbar(Short) !", duration = SnackbarDuration.Short // デフォルト ) } // _scope.launch { // _hostState.showSnackbar( // message = "Snackbar(Long) !", // duration = SnackbarDuration.Long // ) // } // _scope.launch { // _ihostState.showSnackbar( // message = "Snackbar(Indefinite) !", // duration = SnackbarDuration.Indefinite // ) // } }, modifier = Modifier.padding(10.dp) ) { Text(text = "Push") }
手動で閉じる
「duration = SnackbarDuration.Indefinite」を指定すると、自動で閉じなくなります。
このような場合、DismissAction(×アイコン)を付加して、手動で閉じるようにします。
val _scope = rememberCoroutineScope() val _hostState = remember { SnackbarHostState() } Button( onClick = { _scope.launch { _hostState.showSnackbar( message = "Snackbar(Indefinite) !", duration = SnackbarDuration.Indefinite, withDismissAction = true ) } }, modifier = Modifier.padding(10.dp) ) { Text(text = "Push") }
閉じたときの処理
SnackbarHostState#showSnackbar関数は、バーが閉じた理由を返します。
SnackbarResult.Dismissedは、自動もしくはDismissAction(×アイコン)で閉じられたことを示しています。
ですので、これを条件にすれば、バーが閉じた時の処理を追加できます。
val _scope = rememberCoroutineScope() val _hostState = remember { SnackbarHostState() } Button( onClick = { _scope.launch { val _result = _hostState.showSnackbar( message = "Snackbar(Short) !", duration = SnackbarDuration.Short // デフォルト ) when(_result) { SnackbarResult.ActionPerformed -> {} SnackbarResult.Dismissed -> { /* バーが閉じた時の処理 */ } } } modifier = Modifier.padding(10.dp) ) { Text(text = "Push") }
アクションを追加
Actionをクリックするとバーは閉じられます。
また、SnackbarHostState#showSnackbar関数は、バーが閉じた理由を返します。
SnackbarResult.ActionPerformedは、Actionをクリックすることで閉じられたことを示しています。
ですので、これを条件にすれば、Actionをクリックした時の処理を追加できます。
val _scope = rememberCoroutineScope() val _hostState = remember { SnackbarHostState() } Button( onClick = { _scope.launch { val _result =_hostState.showSnackbar( message = "Snackbar(Action) !", actionLabel = "Action", ) when(_result) { SnackbarResult.ActionPerformed -> { /* アクションをクリックした時の処理 */ } SnackbarResult.Dismissed -> {} } } }, modifier = Modifier.padding(10.dp) ) { Text(text = "Push") }
ちなみに、「actionLabel = “文字列”」に連動して「duration = SnackbarDuration.Indefinite」になります。※「関数の引数」を参照
カスタムSnackbar
Snackbarはタイプ1とタイプ2があります。
タイプ1は、DismissActionがIcon(×アイコン)、ActionがText、そして、コンテンツがTextで表現されていて、固定です。
タイプ2は、全部がスロットになっているので、自由にカスタマイズできます。
DismissActionのカスタマイズ
val _scope = rememberCoroutineScope() val _hostState = remember { SnackbarHostState() } SnackbarHost( hostState = _hostState, modifier = Modifier.align(Alignment.BottomCenter) ) { Snackbar( // タイプ2、itはSnackbarDataインスタンス modifier = Modifier.padding(10.dp), dismissAction = { Text( text = "Close", modifier = Modifier.padding(end = 20.dp) .clickable { it.dismiss() } // クリックで閉じる ) } ) { Text(text = it.visuals.message) } } Button( onClick = { _scope.launch { val _result = _hostState.showSnackbar( message = "Snackbar !", duration = SnackbarDuration.Indefinite ) when(_result) { SnackbarResult.ActionPerformed -> {} SnackbarResult.Dismissed -> { /* バーを閉じた時の処理 */ } } } }, modifier = Modifier.padding(10.dp) ) { Text(text = "Push") }
Actionのカスタマイズ
val _scope = rememberCoroutineScope() val _hostState = remember { SnackbarHostState() } SnackbarHost( hostState = _hostState, modifier = Modifier.align(Alignment.BottomCenter) ) { Snackbar( // タイプ2、itはSnackbarDataインスタンス modifier = Modifier.padding(10.dp), action = { IconButton(onClick = { it.performAction() }) { // クリックで閉じる Icon( imageVector = Icons.Filled.Settings, contentDescription = "Settings" ) } }, ) { Text(text = it.visuals.message) } } Button( onClick = { _scope.launch { val _result = _hostState.showSnackbar( message = "Snackbar !", duration = SnackbarDuration.Indefinite ) when(_result) { SnackbarResult.ActionPerformed -> { /* アクションをクリックした時の処理 */ } SnackbarResult.Dismissed -> {} } } }, modifier = Modifier.padding(10.dp) ) { Text(text = "Push") }
2段構成のバー
コンテンツに表示するメッセージが長い時のために、アクションを別の行へ分離することが出来ます。
val _scope = rememberCoroutineScope() val _hostState = remember { SnackbarHostState() } Button( onClick = { _scope.launch { _hostState.showSnackbar( message = "This is a snackbar with a long message, ...", actionLabel = "Action" ) } }, modifier = Modifier.padding(10.dp) ) { Text(text = "Push") } SnackbarHost( hostState = _hostState, modifier = Modifier.align(Alignment.BottomCenter) ){ Snackbar( // タイプ1(タイプ2も同様) snackbarData = it, // actionOnNewLine = false // デフォルト actionOnNewLine = true ) }
関連記事: