You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让Compose动画在每次点击触发时强制重置?

问题解决:强制每次点击重启闪行动画

问题原因

当前实现中,快速点击时旧的延迟协程仍在运行,会在500ms后强制将状态设为false;同时AnimatedVisibility的过渡状态未被重置,导致无法重新触发进入动画,只能继续执行退出动画。

解决方案

通过取消旧任务+强制重置动画组件的方式,确保每次点击都启动全新的动画周期:

1. 修改状态管理类

在DefaultFlashBoxState中保存并取消旧的延迟任务,同时新增计数器用于重置动画组件:

interface FlashBoxState {
    val isVisible: MutableTransitionState<Boolean>
    val flashCount: State<Int>
    fun flash()
}

private class DefaultFlashBoxState(
    private val coroutineScope: CoroutineScope,
) : FlashBoxState {
    override val isVisible = MutableTransitionState(false)
    private val _flashCount = mutableStateOf(0)
    override val flashCount: State<Int> = _flashCount
    private var flashJob: Job? = null

    override fun flash() {
        // 取消之前未完成的延迟任务
        flashJob?.cancel()
        // 递增计数器,触发动画组件重建
        _flashCount.value++
        // 启动新的显示动画
        isVisible.targetState = true
        // 启动新的延迟隐藏任务
        flashJob = coroutineScope.launch {
            delay(500)
            isVisible.targetState = false
        }
    }
}

@Composable
fun rememberFlashBoxState(): FlashBoxState {
    val coroutineScope = rememberCoroutineScope()
    return remember { DefaultFlashBoxState(coroutineScope) }
}

2. 重置动画组件

在FlashBox的AnimatedVisibility中添加key参数,使用计数器强制每次点击重建动画:

@Composable
fun FlashBox(
    state: FlashBoxState,
    content: @Composable () -> Unit
) {
    Box {
        content()
        Column(modifier = Modifier.align(Alignment.BottomCenter)) {
            AnimatedVisibility(
                key = state.flashCount.value, // 新增key,强制重建动画
                visibleState = state.isVisible,
                enter = fadeIn(animationSpec = tween(durationMillis = 200)) +
                        slideIn(
                            animationSpec = tween(durationMillis = 200),
                            initialOffset = { size -> IntOffset(0, y = size.height) }
                        ),
                exit = fadeOut(animationSpec = tween(durationMillis = 1000)) +
                        slideOut(
                            animationSpec = tween(durationMillis = 1000),
                            targetOffset = { size -> IntOffset(x = 0, y = size.height * (-6)) },
                        ),
            ) {
                Text(text = "Copied!", textAlign = TextAlign.Center, modifier = Modifier.width(150.dp))
            }
        }
    }
}

更直观的简化实现

可以去掉单独的状态类,直接在FlashBox内部处理状态逻辑,代码更简洁:

@Composable
fun FlashBox(
    content: @Composable (onFlash: () -> Unit) -> Unit
) {
    var showFlash by remember { mutableStateOf(false) }
    var flashTrigger by remember { mutableStateOf(0) }

    Box {
        // 给子组件传递触发动画的函数
        content {
            flashTrigger++
            showFlash = true
        }

        Column(modifier = Modifier.align(Alignment.BottomCenter)) {
            AnimatedVisibility(
                key = flashTrigger,
                visible = showFlash,
                enter = fadeIn(tween(200)) + slideIn(tween(200)) { IntOffset(0, it.height) },
                exit = fadeOut(tween(1000)) + slideOut(tween(1000)) { IntOffset(0, -it.height * 6) }
            ) {
                Text(
                    text = "Copied!",
                    textAlign = TextAlign.Center,
                    modifier = Modifier.width(150.dp)
                )
            }
        }
    }

    // 自动延迟隐藏
    LaunchedEffect(flashTrigger) {
        delay(500)
        showFlash = false
    }
}

// 使用方式
FlashBox { onFlash ->
    CodePointCell(
        size = 150.dp,
        onClick = onFlash
    )
}

内容的提问来源于stack exchange,提问作者Hakanai

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 05:35:56