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

