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

如何让Jetpack Compose LazyGrid项仅首次显示时执行气泡动画?

Jetpack Compose LazyGrid气泡式入场动画:重组导致动画判断失效问题解决

问题背景

为LazyGrid实现气泡式入场动画时,尝试通过维护已动画索引集合来避免重复动画,但因LazyGrid Item发生两次重组,导致判断条件失效,动画无法正确只执行一次。

问题根源

  1. 直接修改可变集合触发异常重组:在Composable中直接操作remember { mutableSetOf() },该集合不是可观察状态,修改后无法被Compose正确跟踪;同时LazyGrid的Item会因布局测量、状态更新等原因发生多次重组,导致animatedIndices.add(idx)被重复执行,判断逻辑失效。
  2. 动画状态未与Item绑定:原动画逻辑未针对每个Item独立保存状态,重组时会重新初始化动画,导致重复触发。

解决方案

1. 使用可观察状态集合跟踪已动画索引

用mutableStateSetOf替代普通mutableSetOf,配合rememberSaveable确保状态可观察且在配置变更时保留。

2. 用LaunchedEffect确保操作只执行一次

针对每个Item索引,使用LaunchedEffect包裹添加索引的逻辑,确保仅在Item首次组合时执行一次。

3. 独立保存每个Item的动画状态

让每个Item的动画状态通过remember与自身索引绑定,避免重组时重新初始化动画。

修改后的完整代码

Gallery组件修改

@Composable
private fun Gallery(
    paddingValues: PaddingValues,
    uiConfig: () -> List<String>
) {
    val config: List<String> = uiConfig()
    val columns = 2

    // 使用mutableStateSetOf实现可观察状态集合,rememberSaveable保存状态
    val animatedIndices = rememberSaveable { mutableStateSetOf<Int>() }

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(paddingValues)
    ) {
        LazyVerticalGrid(
            columns = GridCells.Fixed(columns),
            modifier = Modifier.fillMaxSize(),
            verticalArrangement = Arrangement.spacedBy(8.dp),
            horizontalArrangement = Arrangement.spacedBy(8.dp),
            content = {
                items(config.size) { idx ->
                    val item: String = config[idx]
                    // 针对每个idx保存shouldAnimate状态,避免重组时重新计算
                    val shouldAnimate = remember(idx) { !animatedIndices.contains(idx) }

                    // LaunchedEffect以idx为key,确保每个Item仅执行一次添加操作
                    LaunchedEffect(idx) {
                        if (shouldAnimate) {
                            animatedIndices.add(idx)
                        }
                    }

                    val (scale, alpha) = if (shouldAnimate) {
                        scaleAndAlpha(idx, columns)
                    } else {
                        1f to 1f // 无动画状态
                    }

                    MyItem(
                        modifier = Modifier.graphicsLayer(alpha = alpha, scaleX = scale, scaleY = scale),
                        text = item
                    )
                }
            }
        )
    }
}

scaleAndAlpha函数优化

@OptIn(ExperimentalTransitionApi::class)
@Composable
fun scaleAndAlpha(
    idx: Int,
    columns: Int,
    args: ScaleAndAlphaArgs = ScaleAndAlphaArgs(2f, 1f, 0f, 1f)
): Pair<Float, Float> {
    // 用remember保存每个Item的过渡状态,确保仅初始化一次
    val transitionState = remember { MutableTransitionState(State.PLACING).apply { targetState = State.PLACED } }
    val transition = rememberTransition(transitionState, label = "item_animation_$idx")
    // 针对每个idx缓存动画参数
    val animationSpec = remember(idx, columns) {
        tween(300, delayMillis = (idx / columns) * 100)
    }

    val alpha by transition.animateFloat(transitionSpec = { animationSpec }, label = "alpha_$idx") {
        if (it == State.PLACING) args.fromAlpha else args.toAlpha
    }
    val scale by transition.animateFloat(transitionSpec = { animationSpec }, label = "scale_$idx") {
        if (it == State.PLACING) args.fromScale else args.toScale
    }
    return alpha to scale
}

关键说明

  • mutableStateSetOf:使集合的增删操作能被Compose观察,确保状态变更时触发正确的重组,而非静默修改。
  • LaunchedEffect(idx):以Item索引为key,确保每个Item的添加索引操作仅在首次组合时执行,避免重组重复触发。
  • remember(idx):为每个Item独立保存判断状态,防止重组时重新计算导致判断逻辑混乱。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 22:40:56