简体   繁体   English

如何在 Jetpack Compose 中显示带按钮 onclick 的小吃店

[英]How to show snackbar with a button onclick in Jetpack Compose

I want to show snackbar with a button onclick in Jetpack Compose我想在 Jetpack Compose 中显示带有按钮 onclick 的小吃店
I tried this我试过这个

Button(onClick = {
    Snackbar(action = {}) {
        Text("hello")
    }
} 

But AS said "@Composable invocations can only happen from the context of a @Composable function"但是正如所说“@Composable 调用只能在@Composable 函数的上下文中发生”
Shall you give me a nice program.你能给我一个好的程序吗?
I wish it can run in Button.onclick()我希望它可以在 Button.onclick() 中运行

You'll need two things:你需要两件事:

  1. SnackbarHostState - which will manage the state of your Snackbar ( you would usually get that from the ScaffoldState ) SnackbarHostState - 它将管理您的Snackbar的状态(您通常会从ScaffoldState获得它
  2. CoroutineScope - which will be responsible for showing your Snackbar CoroutineScope - 负责showing你的Snackbar

@Composable
fun SnackbarDemo() {
  val scaffoldState = rememberScaffoldState() // this contains the `SnackbarHostState`
  val coroutineScope = rememberCoroutineScope()

  Scaffold(
        modifier = Modifier,
        scaffoldState = scaffoldState // attaching `scaffoldState` to the `Scaffold`
    ) {
        Button(
            onClick = {
                coroutineScope.launch { // using the `coroutineScope` to `launch` showing the snackbar
                    // taking the `snackbarHostState` from the attached `scaffoldState`
                    val snackbarResult = scaffoldState.snackbarHostState.showSnackbar(
                        message = "This is your message",
                        actionLabel = "Do something."
                    )
                    when (snackbarResult) {
                        Dismissed -> Log.d("SnackbarDemo", "Dismissed")
                        ActionPerformed -> Log.d("SnackbarDemo", "Snackbar's button clicked")
                    }
                }
            }
        ) {
            Text(text = "A button that shows a Snackbar")
        }
    }
}

例子

Building on Bartek's answer you can also use Compose's side-effects .基于 Bartek 的回答,您还可以使用Compose 的副作用 Then you don't have to manage the CoroutineScope itself.然后您不必管理CoroutineScope本身。

Since Composables itself should be side-effect free , it is recommended to make use of Compose's Effect APIs so that those side effects are executed in a predictable manner.由于Composables本身应该没有副作用,因此建议使用Compose's Effect APIs ,以便以可预测的方式执行这些副作用。

In your specific case you can use the LaunchedEffect API to run suspend functions in the scope of a composable.在您的特定情况下,您可以使用LaunchedEffect API在可组合的 scope 中运行挂起功能。 The example code would look like the following:示例代码如下所示:

@Composable
fun SnackbarDemo() {
    val scaffoldState = rememberScaffoldState() // this contains the `SnackbarHostState`
    val (showSnackBar, setShowSnackBar) = remember {
        mutableStateOf(false)
    }
    if (showSnackBar) {
        LaunchedEffect(scaffoldState.snackbarHostState) {
            // Show snackbar using a coroutine, when the coroutine is cancelled the
            // snackbar will automatically dismiss. This coroutine will cancel whenever
            // `showSnackBar` is false, and only start when `showSnackBar` is true
            // (due to the above if-check), or if `scaffoldState.snackbarHostState` changes.
            val result = scaffoldState.snackbarHostState.showSnackbar(
                message = "Error message",
                actionLabel = "Retry message"
            )
            when (result) {
                SnackbarResult.Dismissed -> {
                    setShowSnackBar(false)
                }
                SnackbarResult.ActionPerformed -> {
                    setShowSnackBar(false)
                    // perform action here
                }
            }
        }
    }
    Scaffold(
        modifier = Modifier,
        scaffoldState = scaffoldState // attaching `scaffoldState` to the `Scaffold`
    ) {
        Button(
            onClick = {
                setShowSnackBar(true)
            }
        ) {
            Text(text = "A button that shows a Snackbar")
        }
    }
}

If you use new Material3 there is new field in Scaffold: "snackbarHost"如果您使用新的 Material3,Scaffold 中有一个新字段:“snackbarHost”

val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope()

Scaffold(
  snackbarHost = { SnackbarHost(snackbarHostState) },
  ...

            // for showing snackbar in onClick for example:
            scope.launch {
                snackbarHostState.showSnackbar(
                    "Snackbar Test"
                )
            }

Just in case you need a snackbar as a separate function to call in other screens with custom parameters, and without using scaffold, I use this below.以防万一您需要一个 snackbar 作为单独的 function 来使用自定义参数调用其他屏幕,而不使用脚手架,我在下面使用它。

The parameter openSnackbar: (Boolean) -> Unit is used to reset the opening condition var openMySnackbar by remember { mutableStateOf(false) } and allow open snackbar other times.参数openSnackbar: (Boolean) -> Unit用于var openMySnackbar by remember { mutableStateOf(false) }并允许其他时间打开 snackbar。

    @Composable
    fun SnackbarWithoutScaffold(
        message: String,
        showSb: Boolean,
        openSnackbar: (Boolean) -> Unit
    ) {
    
        val snackState = remember { SnackbarHostState() }
        val snackScope = rememberCoroutineScope()
    
        SnackbarHost(
            modifier = Modifier,
            hostState = snackState
        ){
            Snackbar(
                snackbarData = it,
                backgroundColor = Color.White,
                contentColor = Color.Blue
            )
        }
    
    
        if (showSb){
            LaunchedEffect(Unit) {
                snackScope.launch { snackState.showSnackbar(message) }
                openSnackbar(false)
            }
   
        }
    
    
    }

And used like this:并像这样使用:

@Composable fun MyScreen() { @Composable fun MyScreen() {

var openMySnackbar by remember { mutableStateOf(false)  }
var snackBarMessage by remember { mutableStateOf("") }

Column() {

    Button(
        shape = RoundedCornerShape(50.dp),
        onClick = {
            openMySnackbar = true
            snackBarMessage = "Your message"
        },
    ) {
        Text(text = "Open Snackbar")
    }

    SnackbarWithoutScaffold(snackBarMessage, openMySnackbar, , {openMySnackbar = it})
}

} }

You can use this你可以用这个

@Composable
fun MainScreen() {
    val coroutineScope = rememberCoroutineScope()
    val showSnackBar: (
        message: String?,
        actionLabel: String,
        actionPerformed: () -> Unit,
        dismissed: () -> Unit
    ) -> Unit = { message, actionLabel, actionPerformed, dismissed ->
        coroutineScope.launch {
            val snackBarResult = scaffoldState.snackbarHostState.showSnackbar(
                message = message.toString(),
                actionLabel = actionLabel
            )
            when (snackBarResult) {
                SnackbarResult.ActionPerformed -> actionPerformed.invoke()
                SnackbarResult.Dismissed -> dismissed.invoke()
            }
        }
    }


    showSnackBar.invoke(
        "YOUR_MESSAGE",
        "ACTION_LABEL",
        {
         //TODO ON ACTION PERFORMED
        },
        {
         //TODO ON DISMISSED
        }
    )
}

Let me add an other answer, i was not using scaffold and was using state hoisting让我添加另一个答案,我没有使用脚手架,而是使用 state 吊装

val loginState by viewModel.uiState.collectAsState() // STATE HOISTING
val snackbarHostState = remember { SnackbarHostState() }
val message = stringResource(id = R.string.error_login) 
if (loginState.snackbarVisible) { // When passing true, show snackbar
    LaunchedEffect(snackbarHostState) {
        snackbarHostState.showSnackbar(
            message = message,
            actionLabel = "Do something."
        )
    }
}


Box(
    modifier = Modifier.fillMaxSize()
) {
    // Your screen content
    // ...
    SnackbarHost(
        hostState = snackbarHostState,
        modifier = Modifier.align(Alignment.BottomCenter)
    ) // Snackbar location
}

First of all you need a SnackbarHostState, you can pass this state down to your composable where you want to trigger a snackbar message.首先,您需要一个 SnackbarHostState,您可以将此 state 传递给您想要触发小吃栏消息的组合。 or if you use a scaffold use that one scaffoldState.snackbarHostState或者,如果您使用脚手架,请使用该scaffoldState.snackbarHostState

val snackbarHostState = remember { SnackbarHostState() }

Showing a snackbar is a side effect and should be wrapped in a LaunchEffect Composable显示一个snackbar是一个副作用,应该被包裹在一个LaunchEffect Composable中

@Composable
fun MyScreen(
    scaffoldState: ScaffoldState = rememberScaffoldState()
) {
   Scaffold(scaffoldState = scaffoldState) {    

     Button(onClick = {
      LaunchedEffect(scaffoldState.snackbarHostState) {
         scaffoldState.snackbarHostState.showSnackbar(
                  message = "hello world",
                  actionLabel = "Retry"
         )
      }

    
     // ...
    }
}

See more information here https://developer.android.com/topic/architecture/ui-layer/events#consuming-trigger-updates在此处查看更多信息https://developer.android.com/topic/architecture/ui-layer/events#sumption-trigger-updates

and https://developer.android.com/jetpack/compose/side-effectshttps://developer.android.com/jetpack/compose/side-effects

You can show snackBar without Scaffold using SnackBar Composable either.您也可以使用SnackBar Composable 来显示没有 Scaffold 的snackBar。

Box(modifier = Modifier.fillMaxSize()) {
    var showSnackbar by remember {
        mutableStateOf(false)
    }

    LaunchedEffect(key1 = showSnackbar) {
        if (showSnackbar) {
            delay(2000)
            showSnackbar = false
        }
    }


    Column {

        Button(onClick = {
            showSnackbar = !showSnackbar
        }) {
            Text("Show Snackbar")
        }
    }

    if (showSnackbar) {
        androidx.compose.material.Snackbar(modifier = Modifier.align(Alignment.BottomStart),
            action = {
                Text(text = "Action",
                    color = Color(0xffCE93D8),
                    modifier = Modifier.clickable {
                        showSnackbar = false
                    }
                )
            }) {
            androidx.compose.material.Text("Message")
        }
    }
}

LaunchedEffect is to remove it from composition. LaunchedEffect 是将其从合成中移除。 You can use a slide animation if you want to.如果您愿意,可以使用幻灯片 animation。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM