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

如何在Jetpack Compose中同步多行组件的不同动画?

问题解决:Jetpack Compose两行动画同步执行

问题概述

开发Jetpack Compose应用时,需要实现两行组件的同步动画:第一行执行缩放切换动画,第二行执行CrossFade切换动画。当前实现中第二行动画始终滞后于第一行,期望两者同时触发,动画结束后保持显示2秒,再循环重复。

原代码问题分析

原实现中,AnimateRotatingList内部先完成缩放动画,结束后才更新索引,导致依赖索引变化的CrossFade动画只能在第一行动画结束后启动,造成时序差异。

修改方案及代码

核心思路

将动画调度和状态管理统一移到父组件,让两行动画共享同一时序控制,确保索引切换和动画启动同步进行。

修改后的完整代码

@Composable
fun <T> AnimateRotatingList(
    list: List<T>,
    currentIndex: Int,
    animationProgress: Float,
    modifier: Modifier = Modifier,
    itemContent: @Composable (item: T) -> Unit
) {
    Box(modifier = modifier) {
        if (list.isEmpty()) return
        if (list.size == 1) {
            itemContent(list.first())
            return
        }

        val current = list[currentIndex]
        val next = list[(currentIndex + 1) % list.size]

        Box(modifier = Modifier.graphicsLayer {
            scaleY = 1 - animationProgress
            transformOrigin = TransformOrigin(0.5f, 0f)
        }) {
            itemContent(current)
        }

        Box(modifier = Modifier.graphicsLayer {
            scaleY = animationProgress
            transformOrigin = TransformOrigin(0.5f, 1f)
        }) {
            itemContent(next)
        }
    }
}

data class Event(
    val start: String,
    val end: String,
    val loc: String,
    val loca: String
)

@Composable
fun FollowEventCardHome() {
    val eventData = listOf(
        Event("A", "B", "T1", "T2"),
        Event("B", "C", "T3", "T4"),
        Event("C", "D", "T5", "T6"),
    )

    val animationSpec = tween<Float>(durationMillis = 2000)
    val animationProgress = remember { Animatable(0f) }
    var currentIndex by remember(eventData) { mutableIntStateOf(0) }
    var crossFadeTargetIndex by remember(eventData) { mutableIntStateOf(currentIndex) }

    LaunchedEffect(Unit) {
        while (true) {
            // 保持当前状态2秒
            delay(2000)
            // 提前切换CrossFade目标索引,触发同步动画
            crossFadeTargetIndex = (currentIndex + 1) % eventData.size
            // 执行缩放动画
            animationProgress.animateTo(1f, animationSpec)
            // 动画结束后更新主索引,重置动画进度
            currentIndex = crossFadeTargetIndex
            animationProgress.snapTo(0f)
        }
    }

    Column {
        AnimateRotatingList(
            list = eventData,
            currentIndex = currentIndex,
            animationProgress = animationProgress.value,
            modifier = Modifier.padding(top = 150.dp)
        ) {
            Row(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(horizontal = 30.dp),
                horizontalArrangement = Arrangement.SpaceBetween,
                verticalAlignment = Alignment.CenterVertically,
            ) {
                Text(text = it.start, fontSize = 20.sp)
                Text(text = it.end, fontSize = 20.sp)
            }
        }

        Spacer(modifier = Modifier.padding(20.dp))

        Crossfade(
            targetState = crossFadeTargetIndex,
            animationSpec = animationSpec // 共享动画规格,保证时序一致
        ) { index ->
            val currentItem = eventData[index]
            Row(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(horizontal = 30.dp),
                horizontalArrangement = Arrangement.SpaceBetween,
                verticalAlignment = Alignment.CenterVertically,
            ) {
                Text(text = currentItem.loc)
                Text(text = currentItem.loca)
            }
        }
    }
}

关键改动点

  • 统一状态调度:父组件维护animationProgress动画进度,AnimateRotatingList仅负责根据进度渲染缩放效果,避免子组件独立调度造成的时序偏差。
  • 同步索引切换:在缩放动画启动前,先更新crossFadeTargetIndex,让CrossFade动画与缩放动画同时触发。
  • 共享动画参数:CrossFade使用和缩放动画相同的animationSpec,确保两者的动画时长、曲线完全匹配。
  • 循环逻辑优化:动画结束后重置进度并更新主索引,保证下一轮循环的初始状态正确。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 08:55:54