未调用 Jetpack Compose 动画完成的侦听器

Jetpack Compose animation finished listener not called

我正在努力使用 Jetpack Compose Animation 来实现(据说很简单)的效果: 在出现错误的情况下,控件的背景颜色应闪烁红色,并在短暂延迟后逐渐变回正常(透明)。

我目前的方法是使用布尔状态 shouldFlash 对其进行建模,当发生错误时该状态设置为 true,并在动画完成时设置回 false。不幸的是,传递给 animateColorAsStatefinishedListener 似乎从未被调用过。我附加了一个调试器并添加了一个日志语句来验证这一点。

我做错了什么?

示例(按钮触发错误):

@Composable
fun FlashingBackground() {
    Column(modifier = Modifier.size(200.dp)) {
        var shouldFlash by remember { mutableStateOf(false) }
        var text by remember { mutableStateOf("Initial") }
        FlashingText(flashing = shouldFlash, text = text) {
            shouldFlash = false
            text = "Animation done"
        }

        Button(onClick = {
            shouldFlash = true
        }) {
            Text(text = "Flash")
        }
    }
}

@Composable
fun FlashingText(flashing: Boolean, text: String, flashFinished: () -> Unit) {
    if (flashing) {
        val flashColor by animateColorAsState(
            targetValue = Color.Red,
            finishedListener = { _ -> flashFinished() }
        )
        Text(text = text, color = Color.White, modifier = Modifier.background(flashColor))
    } else {
        Text(text = text)
    }
}

Compose 版本:1.0.5(撰写本文时最新稳定版),也在 1.1.0-beta02

无论如何,要理解这一点,您需要了解 animateColorAsState 的内部工作原理。

它依赖于重组——这是核心思想。

每次颜色变化时,都会触发重新合成,导致更新后的颜色值反映在屏幕上。现在,您所做的只是使用条件语句来显示 DIFFERENT 组合。现在,一个 Composable 实际上指的是动画值,即 if 块内的值(当 flashing 为真时)。另一方面,else 块 Composable 只是一个不引用它的常规文本。这就是为什么你需要删除条件。无论如何,因为在删除条件后,剩下的只是一个文本,我认为从中创建一个全新的 Composable 是一种浪费,这就是为什么我完全删除了那个 Composable 并将 Text 粘贴到里面您的主要可组合项。它有助于使事情足够简单。除此之外,@Rafiul 的答案确实有效,但实际上并不需要这样的 Composable,所以我仍然建议改用这个答案,这样代码更容易阅读。

原始答案:

尝试将动画师移出子可组合项

@Composable
fun FlashingBackground() {
    Column(modifier = Modifier.size(200.dp)) {
        var shouldFlash by remember { mutableStateOf(false) }
        var text by remember { mutableStateOf("Initial") }
        val flashFinished: (Color) -> Unit = {
            shouldFlash = false
            text = "Animation done"
        }
        val flashColor by animateColorAsState(
            targetValue = if (shouldFlash) Color.Red else Color.White,
            finishedListener = flashFinished
        )

        //FlashingText(flashing = shouldFlash, text = text) -> You don't need this
        Text(text = text, color = Color.White, modifier = Modifier.background(flashColor))
        Button(onClick = {
            shouldFlash = true
        }) {
            Text(text = "Flash")
        }
    }
}

像这样更改您的代码。

闪烁背景

@Composable
fun FlashingBackground() {
    Column(modifier = Modifier.size(200.dp)) {
        var shouldFlash by remember { mutableStateOf(false) }
        var text by remember { mutableStateOf("Initial") }
        FlashingText(flashing = shouldFlash, text = text) {
            shouldFlash = false
            text = "Animation done"
        }

        Button(onClick = {
            shouldFlash = true
        }) {
            Text(text = "Flash")
        }
    }
}

FlashingText

@Composable
fun FlashingText(flashing: Boolean, text: String, flashFinished: () -> Unit) {
        val flashColor by animateColorAsState(
            targetValue = if(flashing) Color.Red else Color.White,
            finishedListener = { _ -> flashFinished() }
        )
        Text(text = text, color = Color.White, modifier = Modifier.background(flashColor))
}

已编辑:

您的代码存在问题,您在单击 Flash 按钮并制作 shouldFlash = true 时正在初始化 animateColorAsState。所以这是第一次,它只初始化 animateColorAsState,而不是 运行 动画。所以也不会有 finishedListener 调用。由于未执行 finishedListener,因此 shouldFlash 保持为 true。所以从下一次调用开始 shouldFlash 已经是 true 不会有状态变化。这就是为什么从随后的按钮单击开始,它不再重新组合 FlashingText 的原因。您可以在您的方法中添加一些日志,您在第一次单击按钮后不会看到 FlashingText。

请记住: targetValue = Color.Red 不会做任何事情。目标值应该是一个状态或者像这样的条件 if(flashing) Color.Red 因为你需要改变状态来启动动画。

@Phillip 的回答也对。但是,如果您像上面那样使用动画师,我看不到将动画师移出子可组合项有任何额外优势。