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

如何在Jetpack Compose中实现物体在两条路径间的平滑运动切换?

解决Jetpack Compose中旋转轨道平滑切换的问题

核心问题分析

原代码通过布尔值直接切换半径,导致半径瞬间突变,出现跳变和卡顿。要实现沿切线平滑过渡,需要让半径从当前值连续动画过渡到目标值,同时保持角度的持续旋转,这样运动轨迹就是自然的螺旋切线路径。

修改后的完整代码

@Composable
fun AnimateObject() {
    // 无限旋转的角度动画,持续运行不中断
    val transition = rememberInfiniteTransition()
    val animatedProgress = transition.animateFloat(
        initialValue = 0f,
        targetValue = 1f,
        animationSpec = infiniteRepeatable(
            animation = tween(durationMillis = 3000, easing = LinearEasing),
            repeatMode = RepeatMode.Restart
        )
    )

    val outerCircleRadius = 450f
    val innerCircleRadius = 250f
    
    // 使用Animatable管理当前半径,支持平滑过渡
    val currentRadius = remember { Animatable(outerCircleRadius) }
    var isInOuterCircle by remember { mutableStateOf(true) }

    val angle = animatedProgress.value * 360f
    val radians = Math.toRadians(angle.toDouble())

    Canvas(modifier = Modifier.size(200.dp), onDraw = {
        val canvasCenter = Offset(size.width / 2, size.height / 2)
        
        // 绘制内外轨道
        drawCircle(
            color = Color.Gray.copy(alpha = 0.3f),
            center = canvasCenter,
            radius = outerCircleRadius,
            style = Stroke(width = 2.dp.toPx())
        )
        drawCircle(
            color = Color.Gray.copy(alpha = 0.3f),
            center = canvasCenter,
            radius = innerCircleRadius,
            style = Stroke(width = 2.dp.toPx())
        )

        // 计算当前位置:角度持续旋转 + 半径平滑过渡
        val x = canvasCenter.x + currentRadius.value * cos(radians).toFloat()
        val y = canvasCenter.y + currentRadius.value * sin(radians).toFloat()

        // 绘制运动的形状
        drawCircle(
            color = Color.Blue,
            center = Offset(x, y),
            radius = 16.dp.toPx()
        )
    })

    Row {
        Button(
            onClick = {
                isInOuterCircle = !isInOuterCircle
                // 启动半径的平滑过渡动画
                coroutineScope.launch {
                    currentRadius.animateTo(
                        targetValue = if (isInOuterCircle) outerCircleRadius else innerCircleRadius,
                        animationSpec = tween(durationMillis = 500, easing = LinearOutSlowInEasing)
                    )
                }
            },
            modifier = Modifier.size(width = 150.dp, height = 50.dp)
        ) {
            Text(text = if (isInOuterCircle) "切换到内圈" else "切换到外圈")
        }
    }
}

关键优化点

  • 用Animatable管理半径:替代直接切换布尔值,点击按钮时启动半径的过渡动画,让半径从当前值平滑变化到目标值
  • 保持角度动画不中断:无限旋转的角度动画持续运行,不会因为切换半径而停顿
  • 移除冗余的状态变量:playerXOffset和playerYOffset不需要单独存状态,直接在Canvas中通过当前半径和角度计算即可,减少不必要的重组
  • 选择合适的缓动函数:使用LinearOutSlowInEasing让过渡更自然,也可以根据需求调整动画时长

效果说明

修改后,点击按钮时蓝色形状会沿着当前运动的切线方向,以螺旋轨迹平滑过渡到目标轨道,全程保持运动状态,不会出现跳变和卡顿。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 06:35:41