compose > Snackbar

1,199 阅读2分钟

学习记录

最开始用到这个

如果用的系统组件scaffold或者backdropscaffold的话,组件里自带snackbar,要显示snackbar只要如下操作即可

//state传给scaffold的构造参数里
val state = rememberBackdropScaffoldState(BackdropValue.Revealed)

//下边的方法要在launch里调用,本身是个suspend方法,可以用LaunchEffect或者rememberCoroutineScope()

state.snackbarHostState.showSnackbar(showContent, "cancel")

scaffold构造方法简化

fun BackdropScaffold(
   //
    scaffoldState: BackdropScaffoldState = rememberBackdropScaffoldState(Concealed),
//
    snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) }
)

snackbarHost 默认有个snackbar组件的

fun SnackbarHost(
    hostState: SnackbarHostState,
    modifier: Modifier = Modifier,
    snackbar: @Composable (SnackbarData) -> Unit = { Snackbar(it) }
) 

snackbar的两种构造方法 看下源码里,这种系统默认写好了,content就是个text,action是个textButton

fun Snackbar(
    snackbarData: SnackbarData,
    modifier: Modifier = Modifier,
    actionOnNewLine: Boolean = false,
    shape: Shape = MaterialTheme.shapes.small,
    backgroundColor: Color = SnackbarDefaults.backgroundColor,
    contentColor: Color = MaterialTheme.colors.surface,
    actionColor: Color = SnackbarDefaults.primaryActionColor,
    elevation: Dp = 6.dp
)

这种构造方法content和action是两个组件,可以自定义,不是上边那种默认写好的,

fun Snackbar(
    modifier: Modifier = Modifier,
    action: @Composable (() -> Unit)? = null,
    actionOnNewLine: Boolean = false,
    shape: Shape = MaterialTheme.shapes.small,
    backgroundColor: Color = SnackbarDefaults.backgroundColor,
    contentColor: Color = MaterialTheme.colors.surface,
    elevation: Dp = 6.dp,
    content: @Composable () -> Unit
) 

没有使用scaffold的情况

其实也简单,仿照scaffold的写法即可,【当然了因为snackbar本身就是个组件,你直接添加这个组件也行,不过得自己处理显示隐藏,而且也没动画,所以一般不这样做,而是利用snackbarhost】

下边举例用法,也很简单:

item{
    val state = remember {
        SnackbarHostState()
    }
    val scope= rememberCoroutineScope()
    SnackbarHost(hostState = state)//默认这个host不显示的,只有state调用show才会显示
    Button(onClick = {
       scope.launch {
           state.showSnackbar("message","close")
       }
    }) {
        Text(text = "click")
    }
}

另外你也可以自定义snackbar的ui,如下

SnackbarHost(hostState = state) {
    Snackbar(action = {
        Icon(painter = painterResource(id = R.drawable.ic_baseline_clear_24),
            contentDescription = "close",Modifier.clickable { it.performAction() })
    }, content = {
        Column {
            Text(text = it.message)
            Text(text = "other compose")
        }
    },
    modifier = Modifier.padding(horizontal = 80.dp),)
}

SnackbarHost 源码分析

简单分析下SnackbarHost是咋做到显示,时间到了隐藏的 先看下数据类

interface SnackbarData {
    val message: String
    val actionLabel: String?
    val duration: SnackbarDuration

    fun performAction()

    fun dismiss()
}

snackbar里边那个action点击会执行performaction方法

onClick = { snackbarData.performAction() },

SnackbarHost的代码

fun SnackbarHost(
    hostState: SnackbarHostState,
    modifier: Modifier = Modifier,
    snackbar: @Composable (SnackbarData) -> Unit = { Snackbar(it) }
) {
    val currentSnackbarData = hostState.currentSnackbarData//开始的时候这个是null的
    val accessibilityManager = LocalAccessibilityManager.current
    LaunchedEffect(currentSnackbarData) {
        if (currentSnackbarData != null) {
            val duration = currentSnackbarData.duration.toMillis(
                currentSnackbarData.actionLabel != null,
                accessibilityManager
            )
            delay(duration)
            currentSnackbarData.dismiss()//恢复挂载函数,snackbardata就会在finally的地方被置空,组件就重组了,snackbarhost会重新加载。
        }
    }
    //current不为空的话下边这个就动画显示snackbar组件了,开始是null,所以不显示的
    FadeInFadeOutWithScale(
        current = hostState.currentSnackbarData,
        modifier = modifier,
        content = snackbar
    )
}

常用的hostState,默认里边数据都是null

//系统组件scaffold里用到的三种
rememberScaffoldState()
rememberBottomSheetScaffoldState()
rememberBackdropScaffoldState(BackdropValue.Revealed)
//普通的
val state = remember {
    SnackbarHostState()
}

调用hostState的show方法的时候才会赋值,如下 这里用到了挂载函数suspendCancellableCoroutine,代码块里初始化了snackbardata,里边的performaction和dismiss的实现方法,作用就是恢复挂载函数

suspend fun showSnackbar(
    message: String,
    actionLabel: String? = null,
    duration: SnackbarDuration = SnackbarDuration.Short
): SnackbarResult = mutex.withLock {
    try {
        return suspendCancellableCoroutine { continuation ->
            currentSnackbarData = SnackbarDataImpl(message, actionLabel, duration, continuation)
            //这里赋值以后,上边的LaunchedEffect(currentSnackbarData)就会开始倒计时执行了。
        }
    } finally {
        currentSnackbarData = null
    }
}

简单看下SnackbarDataImpl,下边两个方法都是用来恢复上边的挂载函数的,挂载函数恢复完以后,就会执行上边的finally,把snackbardata置为null

override fun performAction() {
    if (continuation.isActive) continuation.resume(SnackbarResult.ActionPerformed)
}

override fun dismiss() {
    if (continuation.isActive) continuation.resume(SnackbarResult.Dismissed)
}