如何在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
相关产品推荐
相关产品推荐

