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

Compose中LaunchedEffect链式动画首次执行丢帧问题求助

修复Compose中LaunchedEffect首次动画卡顿丢帧问题

问题根源分析

  • 用mutableStateOf管理动画偏移量,每次赋值都会触发Compose重组,首次动画启动时重组与动画更新冲突,导致帧丢失。
  • animate函数的initialValue硬编码,未与当前实际偏移值绑定,首次动画可能直接跳转到目标值的中间态,跳过部分关键帧。
  • 手动通过launch并行启动多个动画,未利用Compose官方优化的Animatable动画控制器,调度效率偏低。

修复方案

改用Compose官方的Animatable作为动画状态容器,它专门为动画场景设计,能减少不必要的重组,并且提供更稳定的动画调度:

  1. 替换mutableStateOf为Animatable,分别管理X、Y轴偏移。
  2. 使用Animatable.animateTo方法执行动画,确保初始值与当前状态完全同步。
  3. 简化LaunchedEffect逻辑,避免冗余的并行调度操作。

修改后的代码

import androidx.compose.animation.Animatable
import androidx.compose.animation.spring

enum class MenuDraw {
    None, Open, Close
}

fun MenuDraw.isNone() = this == MenuDraw.None
fun MenuDraw.isOpen() = this == MenuDraw.Open

@Composable
fun Demo() {
    var isDraw by remember { mutableStateOf(MenuDraw.None) }
    Box(
        modifier = Modifier
            .fillMaxWidth()
            .fillMaxHeight()
    ) {
        for (i in 0..2) {
            AnimButton1(isDraw, i) {
                isDraw = when (isDraw) {
                    MenuDraw.None -> MenuDraw.Open
                    MenuDraw.Open -> MenuDraw.Close
                    MenuDraw.Close -> MenuDraw.Open
                }
            }
        }
    }
}

@Composable
fun AnimButton1(
    isDraw: MenuDraw,
    index: Int,
    onClick: () -> Unit
) {
    // 用Animatable替代mutableStateOf,初始化到默认位置
    val offsetX = remember { Animatable(0f + index * 100) }
    val offsetY = remember { Animatable(0f + index * 150) }

    LaunchedEffect(isDraw) {
        if (isDraw.isNone()) return@LaunchedEffect

        val targetX = if (isDraw.isOpen()) 200f + index * 100 else 0f + index * 100
        val targetY = if (isDraw.isOpen()) 200f + index * 150 else 0f + index * 150

        // 并行执行X、Y轴动画,Animatable的animateTo调度更高效
        launch {
            offsetX.animateTo(targetX, animationSpec = springSpec(isDraw))
        }
        launch {
            offsetY.animateTo(targetY, animationSpec = springSpec(isDraw))
        }

        if (isDraw.isOpen()) {
            delay(200)
        }
    }

    ShowImage1(offsetX.value, offsetY.value, onClick)
}

@Composable
private fun ShowImage1(
    offsetX: Float,
    offsetY: Float,
    onClick: () -> Unit
) {
    Image(
        painter = painterResource(id = R.drawable.ic_aaa),
        contentDescription = null,
        modifier = Modifier
            .size(50.dp)
            .graphicsLayer(
                translationX = offsetX,
                translationY = offsetY
            )
            .clip(CircleShape)
            .clickableSingle(onClick = onClick)
    )
}

// 自定义弹簧动画配置(保留原逻辑)
fun springSpec(isDraw: MenuDraw) = spring(/* 你的弹簧参数配置 */)

额外优化建议

  • 如果不需要单独控制X、Y轴动画,可以改用Offset类型的Animatable,进一步简化代码结构。
  • 移除动画回调中的日志输出(如原代码中的L.e),IO操作会影响动画性能,调试完成后建议删除。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 05:15:35