这篇我们学习一些 Compose 可以显示/消失的组件
Snackbar
Snackbar 本身就是一个普通的 Compose 组件,单独使用时跟 Text 、Button 这些常用组件没有区别,单纯就是一个显示内容的容器,可以响应 Action 和 DismissAction 个点击事件。
从 Snackbar 函数参数上可以看出它将显示内容和事件响应单独封装到了 SnackbarData 中,像下面这个样子。
这是因为 Snackbar 基本上不会单独使用,而是配合 SnackbarHost 、Scaffold 一起使用。
- Scaffold 提供 SnackbarHost 槽位
- SnackbarHost 中包含 Sanckbar 并实现显示/隐藏动画
- SnackbarHostState.showSnackbar(visual:SnackbarVisuals) 设置显示内容并显示 Snackbar,这是个 suspend 方法。
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SnackbarDemo() {
val scope = rememberCoroutineScope()
val hostState = remember { SnackbarHostState() }
Scaffold(
topBar = { CenterAlignedTopAppBar({
Text(text = "CenterAlignedTopAppBar")
}) },
snackbarHost = { SnackbarHost(hostState = hostState) },
floatingActionButton = {
ExtendedFloatingActionButton(onClick = {
scope.launch {
val result = hostState.showSnackbar(
message = "Snackbar Message",//消息内容文字
// Action 按钮显示文字,设置后会有一个 Action 按钮
actionLabel = "Action",
// 显示时长 Short、Long 参考 Toast,
// Indefinite 一直显示 不会自动消失
duration = SnackbarDuration.Indefinite,
// 是否显示 Dismiss Action 按钮
// true 的话最右边会有一个 X 的按钮
withDismissAction = true
)
when (result) {
//点击了 显示时配置的 Action 按钮
SnackbarResult.ActionPerformed -> {}
//点击了 X 按钮
SnackbarResult.Dismissed -> {}
}
}
}) { Text(text = "Show Snackbar") }
},
bottomBar = { BottomAppBar { Text(text = "bottomBar") } }
) { innerPadding ->
Box(modifier = Modifier.fillMaxSize().padding(innerPadding).background(Color.Magenta))
}
}
SnackbarHostState.showSnackbar(visual:SnackbarVisuals) 方法有两个返回值,当 Snackbar 消失的时候会返回 SnackbarResult.Dismissed ,点击了 Action 按钮返回 SnackbarResult.ActionPerformed ,根据返回值来做接下来的逻辑处理。
这时显示的 Snackbar 配色都是使用默认值创建的,如果想更改配色可以在 SnackbarHost 创建时通过参数传递
Scaffold(
snackbarHost = { SnackbarHost(hostState = hostState, snackbar = { snackbarData ->
Snackbar(snackbarData = snackbarData, containerColor = Color.Yellow)
}) },
这个时候就可以体现出将显示内容封装到 SnackbarData 中的好处了,创建时我也不知道要显示什么,只要定好样式就行了。
甚至于这里连 Snackbar 都可以不要,自定义一个你设计的样式,只要显示 SnackbarData 中的内容并关联事件就行了。
@Composable
fun SnackbarDialog(snackbarData: SnackbarData, modifier: Modifier = Modifier) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 12.dp, vertical = 4.dp)
.background(Color.White, shape = RoundedCornerShape(4.dp)),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "提示", style = MaterialTheme.typography.titleMedium)
Spacer(modifier = Modifier.height(4.dp))
//message
Text(text = snackbarData.visuals.message, style = MaterialTheme.typography.bodyMedium)
Spacer(modifier = Modifier.height(24.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = if (snackbarData.visuals.withDismissAction && snackbarData.visuals.actionLabel != null)
Arrangement.SpaceAround else Arrangement.Center
) {
if (snackbarData.visuals.withDismissAction) {
// dismiss 回调
Button(onClick = { snackbarData.dismiss() }) {
Text(text = "取消")
}
}
snackbarData.visuals.actionLabel?.let {
//performAction 回调
Button(onClick = { snackbarData.performAction() }) {
//actionLabel
Text(text = it)
}
}
}
}
}
显示的位置有问题,想要显示到屏幕中央,只要将 SnackbarHost 在 Scaffold 中的槽位布局到 Scaffold 中央就可以了。
snackbarHost = {
SnackbarHost(hostState = hostState, snackbar = { snackbarData ->
SnackbarDialog(snackbarData = snackbarData)
}, modifier = Modifier.layout{ measurable, constraints ->
val placeable = measurable.measure(constraints)
var hostWidth = 0
var hostHeight = 0
if (placeable.height != 0){
hostWidth = constraints.maxWidth
hostHeight = constraints.maxHeight
}
layout(hostWidth,hostHeight){
if (hostWidth != 0){
val offsetY = constraints.maxHeight / 2
placeable.place(0,offsetY)
}else{
placeable.place(0,0)
}
}
})
},