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

如何同步Jetpack Compose中多个Shimmering Composable的动画?

问题

我希望两个ShimmeringComposable能够同时启动并播放动画,当前实现如下:

LazyColumn(
    .semantics(mergeDescendants = true) { contentDescription = contentDes }
) {
    item { 
        ShimmeringComposable() // 占据页面大部分区域
    } 
   
    item {
        Spacer()
    }
 
    item {
        ShimmeringComposable() // 初始在屏幕外
    }
}

实际运行中,多数情况下第一个Composable的动画会先启动,第二个的动画滞后——哪怕第一个可见、第二个初始在屏幕外时也会出现这个问题。

有没有办法让两者的动画同步,同时启动并播放?

相关实现代码:

@Composable
fun ShimmeringComposable() {
    Box(Modifier
        .size(50.dp)
        .shimmer()
    )
}
fun Modifier.shimmer(): Modifier = composed {
    var size by remember {
        mutableStateOf(IntSize.Zero)
    }
    val width = size.width.toFloat()
    val height = size.height.toFloat()
    val transition = rememberInfiniteTransition()
    val startOffsetX by transition.animateFloat(
        initialValue = -2 * width, 
        targetValue = 2 * width,
        animationSpec = infiniteRepeatable(
            animation = tween(
                durationMillis = 1000,
            )
        )
    )
 
    background(
        brush = Brush.linearGradient(
            colors = listOf(Color.gray,Color.white,Color.gray),
            start = Offset(startOffsetX, 0),
            end = Offset(startOffsetX + width, height)
        )
    ).onGloballyPositioned { size = it.size }
}

补充说明:我判断问题根源在于InfiniteTransition在组件组合时启动动画,但LazyColumn只会组合屏幕可见的项,屏幕外的项要等滚动到可见区域才会被组合。用Column包裹所有Shimmer组件能缓解问题,但Column性能不如LazyColumn,只适合少量组件的场景。


解决方案

核心思路

把动画状态从单个ShimmeringComposable内部提升到父组件,让所有Shimmer组件共享同一个动画进度值——这样不管子组件什么时候被LazyColumn组合,都会使用同步的动画状态。

步骤1:在父组件中创建共享动画状态

在包含LazyColumn的父Composable中,统一创建InfiniteTransition和动画进度值:

@Composable
fun ShimmerParentScreen() {
    // 共享的无限动画过渡,父组件组合时就启动
    val transition = rememberInfiniteTransition()
    // 用父容器宽度作为基准,确保动画覆盖所有子组件
    var parentWidth by remember { mutableStateOf(0f) }
    
    val startOffsetX by transition.animateFloat(
        initialValue = -2 * parentWidth,
        targetValue = 2 * parentWidth,
        animationSpec = infiniteRepeatable(
            animation = tween(durationMillis = 1000)
        )
    )

    LazyColumn(
        modifier = Modifier
            .semantics(mergeDescendants = true) { contentDescription = "shimmer list" }
            .onGloballyPositioned { parentWidth = it.size.width.toFloat() }
    ) {
        item {
            ShimmeringComposable(startOffsetX = startOffsetX)
        }
        item {
            Spacer(modifier = Modifier.height(16.dp))
        }
        item {
            ShimmeringComposable(startOffsetX = startOffsetX)
        }
    }
}

步骤2:修改子组件和Modifier,接收外部动画值

让ShimmeringComposable和shimmer Modifier不再自己创建动画,而是使用父组件传递的同步值:

@Composable
fun ShimmeringComposable(startOffsetX: Float) {
    Box(Modifier
        .size(50.dp)
        .shimmer(startOffsetX = startOffsetX)
    )
}

fun Modifier.shimmer(startOffsetX: Float): Modifier = composed {
    var size by remember {
        mutableStateOf(IntSize.Zero)
    }
    val width = size.width.toFloat()
    val height = size.height.toFloat()

    background(
        brush = Brush.linearGradient(
            colors = listOf(Color.gray, Color.white, Color.gray),
            start = Offset(startOffsetX, 0f),
            end = Offset(startOffsetX + width, height)
        )
    ).onGloballyPositioned { size = it.size }
}

原理说明

  • 动画状态提升到父组件后,InfiniteTransition在父组件初始化时就启动,所有子Shimmer组件共享同一个startOffsetX进度值。
  • 哪怕子组件是后续被LazyColumn延迟组合的,也会直接使用当前的同步动画进度,不会单独启动新的动画,从根本上解决了滞后问题。
  • 保留了LazyColumn的懒加载性能优势,不会因为同步动画而牺牲性能。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 11:27:45