Compose UI:Snackbar

投稿日:  更新日:

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)スナックバーの起動(表示)
単体で使用Scaffoldに組み込み
    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(),
            )
        }
    }
 (1)スナックバーの状態と起動ポイントを作成(SnackbarHostState) 
スナックバーの状態(表示/非表示)を保持します。状態は監視され、変化すると再Composeが行われます。

また、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) }
) { ... }
引数概要
hostStateSnackbarHostStateHostStateのインスタンス
modifierModifierUI全般のCompose修飾子
snackbar@Composable (SnackbarData) -> Unitスナックバーのスロット
 デフォルト:Snackbar(it)(タイプ1)
 ※itはSnackbarDataのインスタンス

Snackbar

タイプ1とタイプ2があります。※詳細は後述の「カスタムSnackbar」を参照

タイプ1タイプ2
@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)SnackbarDataSnackbarDataのインスタンス
 起動時の情報(showSnackbarの引数)
modifierModifierUI全般のCompose修飾子
action(タイプ2)@Composable (() -> Unit)?Actionのスロット
dismissAction(タイプ2)@Composable (() -> Unit)?DismissActionのスロット
actionOnNewLineBooleanActionを別の行に分離(2段構成)
 false:1段構成、true:2段構成
shapeShape角の丸み
containerColorColorバーの色
contentColorColorメッセージの色
actionContentColorColorActionの色
dismissActionContentColorColorDismissActionの色
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 = ...
引数概要
messageString表示するメッセージ
actionLabelStringActionの追加とラベル名
withDismissActionBooleanDismissActionの追加
durationSnackbarDuration表示時間
 SnackbarDuration.Short:5秒程度 ※デフォルト
 SnackbarDuration.Long:10秒程度
 SnackbarDuration.Indefinite:自動で閉じない
スポンサーリンク

関数の構成

Snackbar関数はSurfaceの中にOneRowSnackbarもしくはNewLineButtonSnackbarを格納する構成になっています。

この両者はactionNewLine引数により切り替わります。
※詳細は後述の「2段構成のバー」を参照

Snackbarの構成

 OneRowSnackbar 
1段構成のバーです。メッセージ(content)、アクションは一行に並んで表示されます。

 NewLineButtonSnackbar 
2段構成のバーです。上段はメッセージ(content)、下段はアクションが表示されます。メッセージが長い場合に適した構成です。

スポンサーリンク

バーの配置

Scaffoldに組み込んだ場合

SnackbarをScaffoldに組み込んだ場合は、次の条件を満たした位置にバーが配置されます。

  • (1)アプリバーとフローティングアクションボタンに重ならない
  • (2)コンテンツの最上位レイヤー

Scaffoldに組み込んだ場合

単体で用いた場合

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
    }

UI要素の並び

格納するコンテナの位置

縦に並ぶ中央の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") }
    }

UI要素の重なり

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") }
    }
【Alignment.Center】

Alignment.Center
【Alignment.BottomCenter】

Alignment.BottomCenter
スポンサーリンク

バーの閉じ方

バーを閉じる方法は、自動と手動の2つがあります。

自動で閉じる

指定時間の経過後に自動で閉じます。

指定時間はSnackbarHostState#showSnackbar関数のduration引数で指定し、3つの時間長があります。

  SnackbarDuration.Short
  SnackbarDuration.Long
  SnackbarDuration.Indefinite
:5秒程度    ※デフォルト
: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") }

DismissActionの変更

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") }

Actionの変更

スポンサーリンク

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
            )
        }
【1段構成】

1段構成(actionOnNewLine = false、デフォルト)
actionOnNewLine = false(デフォルト)

【2段構成】

2段構成(actionOnNewLine = true)
actionOnNewLine = true

スポンサーリンク

関連記事:

Jetpack composeは、アプリ開発に必要な一通りのUIコンポーネントをライブラリで提供しています。 そのライブラリ中のButtonについて、構成や使用方法などをまとめます。 ※環境:Android Studio Flamingo | 2022.2.1    :androidx.compose.material3:material3:1.1.1    :androidx.compose.ui:ui:1.4.3 ...
Jetpack composeは、アプリ開発に必要な一通りのUIコンポーネントをライブラリで提供しています。 そのライブラリ中のSurfaceについて、構成や使用方法などをまとめます。 ※環境:Android Studio Flamingo | 2022.2.1    :androidx.compose.material3:material3:1.1.1    :androidx.compose.ui:ui:1.4.3 ...
Jetpack composeは、アプリ開発に必要な一通りのUIコンポーネントをライブラリで提供しています。 そのライブラリ中のRadioButtonについて、構成や使用方法などをまとめます。 ※環境:Android Studio Hedgehog | 2023.1.1     Kotlin 1.8.10     Compose Compiler 1.4.3 ...
Jetpack composeは、アプリ開発に必要な一通りのUIコンポーネントをライブラリで提供しています。 そのライブラリ中にUI要素を格納する機能をもつコンテナ型のUIがあります。 Column/Row/Boxがコンテナ型のUIです。構成や使用方法などをまとめます。 ※環境:Android Studio Hedgehog | 2023.1.1 Patch 1     Kotlin 1.8.10     Compose Compiler 1.4.3 ...
Jetpack composeは、アプリ開発に必要な一通りのUIコンポーネントをライブラリで提供しています。 そのライブラリ中のImageについて、構成や使用方法などをまとめます。 ※環境:Android Studio Hedgehog | 2023.1.1 Patch 2     Kotlin 1.9.0     Compose Compiler 1.5.1     androidx.compose.foundation 1.5.0 ...
Jetpack composeは、アプリ開発に必要な一通りのUIコンポーネントをライブラリで提供しています。 そのライブラリ中のDividerについて、構成や使用方法などをまとめます。 ※環境:Android Studio Hedgehog | 2023.1.1 Patch 2     Kotlin 1.9.0     Compose Compiler 1.5.1     androidx.compose.material3:material3 1.1.1 ...
Jetpack composeは、アプリ開発に必要な一通りのUIコンポーネントをライブラリで提供しています。 そのライブラリ中のSpacerについて、構成や使用方法などをまとめます。 ※環境:Android Studio Hedgehog | 2023.1.1 Patch 2     Kotlin 1.9.0     Compose Compiler 1.5.1     androidx.compose.foundation 1.5.0 ...
Jetpack composeは、アプリ開発に必要な一通りのUIコンポーネントをライブラリで提供しています。 そのライブラリ中のSliderについて、構成や使用方法などをまとめます。 ※環境:Android Studio Hedgehog | 2023.1.1 Patch 2     Kotlin 1.9.0     Compose Compiler 1.5.1     androidx.compose.material3:material3 1.1.1 ...
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 ...
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 ...
Jetpack composeは、アプリ開発に必要な一通りのUIコンポーネントをライブラリで提供しています。 そのライブラリ中のBottomAppBarについて、構成や使用方法などをまとめます。 ※環境:Android Studio Koala | 2024.1.1     Kotlin 1.9.0     Compose Compiler 1.5.1     androidx.compose.material3:material3 1.1.1 ...
Jetpack composeは、アプリ開発に必要な一通りのUIコンポーネントをライブラリで提供しています。 そのライブラリ中のFloatingActionButtonについて、構成や使用方法などをまとめます。 ※環境:Android Studio Koala | 2024.1.1     Kotlin 1.9.0     Compose Compiler 1.5.1     androidx.compose.material3:material3 1.1.1 ...
Jetpack composeは、アプリ開発に必要な一通りのUIコンポーネントをライブラリで提供しています。 そのライブラリ中のLazyColumnについて、構成や使用方法などをまとめます。 ※環境:Android Studio Koala | 2024.1.1     Kotlin 1.9.0     Compose Compiler 1.5.1     androidx.compose.foundation:foundation 1.6.8 ...
Jetpack composeは、アプリ開発に必要な一通りのUIコンポーネントをライブラリで提供しています。 そのライブラリ中のLazyRowについて、構成や使用方法などをまとめます。 ※環境:Android Studio Koala | 2024.1.1 Patch 1     Kotlin 1.9.0     Compose Compiler 1.5.1     androidx.compose.foundation:foundation 1.6.8 ...
スポンサーリンク