18.1 Compose Snackbar

1,560 阅读2分钟

这篇我们学习一些 Compose 可以显示/消失的组件

Snackbar

Snackbar 本身就是一个普通的 Compose 组件,单独使用时跟 Text 、Button 这些常用组件没有区别,单纯就是一个显示内容的容器,可以响应 Action 和 DismissAction 个点击事件。

D21CB58A-D51D-4EAF-BF16-AE171DDD072B.png

从 Snackbar 函数参数上可以看出它将显示内容和事件响应单独封装到了 SnackbarData 中,像下面这个样子。

40CC13A9-8A62-49C9-A229-BD5C5BD20C51.png

这是因为 Snackbar 基本上不会单独使用,而是配合 SnackbarHost 、Scaffold 一起使用。

  1. Scaffold 提供 SnackbarHost 槽位
  2. SnackbarHost 中包含 Sanckbar 并实现显示/隐藏动画
  3. 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 ,根据返回值来做接下来的逻辑处理。

6B74C925-4B52-4E6A-B020-74AB2606CD9A.png

这时显示的 Snackbar 配色都是使用默认值创建的,如果想更改配色可以在 SnackbarHost 创建时通过参数传递

    Scaffold(
        snackbarHost = { SnackbarHost(hostState = hostState, snackbar = { snackbarData ->  
            Snackbar(snackbarData = snackbarData, containerColor = Color.Yellow)
        }) },

1627739F-62D0-40BF-BB75-B38948ACF673.png

这个时候就可以体现出将显示内容封装到 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)
                }
            }
        }
    }
}

91380F8C-7D66-4F54-B53A-C416C42261B2.png

显示的位置有问题,想要显示到屏幕中央,只要将 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)
                    }
                }
            })
        },

23A87C3C-D8F6-4637-BC90-05A80B2ACFF1.png