Snackbar 消息
2026/1/31大约 3 分钟
Snackbar 消息
本笔记覆盖 Jetpack Compose 中的 Snackbar(Material)用法,包含 SnackbarHost / SnackbarHostState、Snackbar、常用参数、显示方式(showSnackbar)、以及与 Toast 的对比、示例代码和最佳实践。
与 toast 消息组件的区别?
这个下面会有操作按钮,而Toast没有
核心概念与组件
SnackbarHostState:用于控制 snackbar 的显示与隐藏,通过showSnackbar()发出消息并接收结果(SnackbarResult)。SnackbarHost:承载 snackbar 的容器,通常放在Scaffold的scaffoldState.snackbarHost中。Snackbar:单个 snackbar 视图,包含message与可选actionLabel。
常用 API
SnackbarHostState.showSnackbar(message: String, actionLabel: String? = null, duration: SnackbarDuration = SnackbarDuration.Short): SnackbarResult:显示 snackbar 并挂起直到用户操作或超时。SnackbarDuration.Short/Long/Indefinite:控制显示时长。ScaffoldState.snackbarHostState:Scaffold提供的默认SnackbarHostState。
简单示例(在 Compose 中显示)
@Composable
fun SimpleSnackbarSample() {
val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(scaffoldState = scaffoldState) { padding ->
Column(Modifier.padding(padding).fillMaxSize(), verticalArrangement = Arrangement.Center) {
Button(onClick = {
scope.launch {
scaffoldState.snackbarHostState.showSnackbar("已保存")
}
}) {
Text("显示 Snackbar")
}
}
}
}使用 actionLabel 与处理结果
scope.launch {
val result = scaffoldState.snackbarHostState.showSnackbar(
message = "文件已删除",
actionLabel = "撤销",
duration = SnackbarDuration.Long
)
when (result) {
SnackbarResult.Dismissed -> { /* 超时或被关闭 */ }
SnackbarResult.ActionPerformed -> { /* 处理撤销逻辑 */ }
}
}自定义样式与位置
SnackbarHost的hostState会放入Scaffold默认位置(屏幕底部),可以提供自定义SnackbarHost和Snackbar以改变样式:
Scaffold(snackbarHost = {
SnackbarHost(it) { data ->
Snackbar(
action = {
data.actionLabel?.let { TextButton(onClick = { /* action */ }) { Text(it) } }
}
) { Text(data.message) }
}
}) { /* content */ }在 Material3 下也可以使用 Snackbar / SnackbarHost 的 Material3 版本(主题与参数略有差异)。
在不使用 Scaffold 的场景
- 也可以直接在顶层放置
SnackbarHost并持有SnackbarHostState,通过协程调用showSnackbar()。
与 Toast 的对比
Snackbar:应用内、可交互、可撤销,易于与应用主题、布局、无障碍集成。Toast:系统级短提示、不可交互,跨 Activity 可见;建议仅在需要系统级提示时使用。
最佳实践
- 优先使用
Snackbar作为应用内反馈(可撤销或有动作的提示)。 - 使用
Scaffold提供的snackbarHostState并在ViewModel层通过事件流(如SharedFlow)触发显示。 - 使用
SnackbarResult判断用户是否执行了 action,并据此执行对应逻辑(如撤销)。 - 对于大量消息,避免堆积 show 调用,考虑去重或队列策略。
进阶:通过 ViewModel 发出 Snackbar 事件
// ViewModel
private val _events = MutableSharedFlow<String>()
val events = _events.asSharedFlow()
fun notify(message: String) { viewModelScope.launch { _events.emit(message) } }
// Composable
@Composable
fun ObserveSnackbar(viewModel: MyViewModel = viewModel()) {
val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
LaunchedEffect(Unit) {
viewModel.events.collect { msg ->
scaffoldState.snackbarHostState.showSnackbar(msg)
}
}
Scaffold(scaffoldState = scaffoldState) { /* content */ }
}速查(Quick reference)
- 显示:
scaffoldState.snackbarHostState.showSnackbar("message") - 带 action:
showSnackbar("msg", "撤销")-> 返回SnackbarResult - 位置:通常由
Scaffold管理,亦可自定义SnackbarHost
package com.example.note.jetpack.examples
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.Scaffold
import androidx.compose.material.SnackbarDuration
import androidx.compose.material.SnackbarHost
import androidx.compose.material.SnackbarHostState
import androidx.compose.material.Text
import androidx.compose.material.rememberScaffoldState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
// Simple standalone Snackbar example using Scaffold and SnackbarHostState
@Composable
fun SimpleSnackbarSample() {
val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(scaffoldState = scaffoldState) { padding ->
Column(modifier = Modifier.padding(16.dp)) {
Button(onClick = {
scope.launch {
scaffoldState.snackbarHostState.showSnackbar(
message = "Hello from Snackbar",
actionLabel = "Undo",
duration = SnackbarDuration.Short
)
}
}, modifier = Modifier.fillMaxWidth()) {
Text("Show Snackbar")
}
}
}
}
// Preview wrapper so the sample can be inspected in the IDE
@Preview(showBackground = true)
@Composable
fun PreviewSimpleSnackbarSample() {
SimpleSnackbarSample()
}
// Example: showing a snackbar in reaction to an event (LaunchedEffect)
@Composable
fun EventTriggeredSnackbar(sampleMessage: String) {
val scaffoldState = rememberScaffoldState()
val snackbarHostState = scaffoldState.snackbarHostState
// show once when sampleMessage changes
LaunchedEffect(sampleMessage) {
if (sampleMessage.isNotEmpty()) {
snackbarHostState.showSnackbar(sampleMessage)
}
}
Scaffold(scaffoldState = scaffoldState) { _ ->
SnackbarHost(hostState = snackbarHostState) { data ->
Text(text = data.message, modifier = Modifier.padding(8.dp))
}
}
}
@Preview(showBackground = true)
@Composable
fun PreviewEventTriggeredSnackbar() {
EventTriggeredSnackbar("Preview event message")
}代码2
package com.example.kt_android_demo.conponses
import android.widget.Toast
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Snackbar
import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import kotlinx.coroutines.launch
@Composable
fun SnackBarComponents() {
// 创建一个 SnackbarHostState
// 用于观察 Snackbar 的状态
val mySnakeBarHostState = remember {
SnackbarHostState()
}
// 创建一个协程作用域
val myCoroutineScope = rememberCoroutineScope()
val ctx = LocalContext.current
/*
snackbarHost: 管理 Snackbar 的状态
*/
Scaffold(
snackbarHost = {
SnackbarHost(
hostState = mySnakeBarHostState,
snackbar = { data ->
// 修改 Snackbar 的样式
Snackbar(
snackbarData = data,
containerColor = Color.Red,
actionColor = Color.Yellow,
contentColor = Color.White,
dismissActionContentColor = Color.Blue,
)
}
)
},
// 屏幕内容
content = { padding ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(padding),
verticalArrangement = Arrangement.Center,
horizontalAlignment = androidx.compose.ui.Alignment.CenterHorizontally
) {
Button(onClick = {
// 操作结果
myCoroutineScope.launch {
// 只能从协程 中调用 showSnackbar(),不阻塞主线程
val result = mySnakeBarHostState.showSnackbar(
message = "this is a snackbar", // 显示的文字
actionLabel = "show Toast", // 显示的按钮
withDismissAction = true, // 是否显示取消按钮
duration = SnackbarDuration.Short // 显示时长
)
if (result == SnackbarResult.ActionPerformed) {
Toast.makeText(ctx, "you click the button", Toast.LENGTH_SHORT).show()
}
}
}) {
Text(text = "Show Snackbar")
}
}
}
)
}