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

Jetpack Compose:如何让LazyRow项仅在页面启动时执行缩放入场动画

解决Jetpack Compose LazyRow项动画仅页面启动执行一次的问题

问题核心在于LazyRow的列表项会在滚出屏幕时被移出Compose组合树,滚回时重新进入组合,导致原代码中LaunchedEffect(true)会重复触发动画。我们可以通过在页面级维护已执行动画的项ID集合,来避免重复播放。

修改步骤

1. 父组件中添加动画状态跟踪集合

在ChooseUI中创建一个MutableSet来记录已经播放过动画的项ID,这个集合和页面生命周期绑定,不会因为列表滚动而丢失状态:

@Composable
fun ChooseUI(
    modifier: Modifier,
    navAction: () -> Unit,
    count: Int
) {
    // 记录已完成动画的项ID,和页面生命周期绑定
    val animatedIds = remember { mutableStateSetOf<Int>() }
    
    ...
    
    LazyRow(
            modifier = modifier,
            contentPadding = PaddingValues(horizontal = 8.dp),
            horizontalArrangement = Arrangement.spacedBy(8.dp)
        ) {
            items(count = count,
                key = { it }) { controlId ->
                CustomControlCard(
                    controlId = controlId,
                    modifier = modifier,
                    navAction = {},
                    packageName = packageName,
                    resourceFolder = resourceFolder,
                    animatedIds = animatedIds // 传递状态集合给子组件
                )
            }
        }
    }
}

2. 子组件中控制动画触发逻辑

修改CustomControlCard,仅在项首次出现且未执行过动画时播放动画,同时设置正确的初始缩放值避免视觉闪烁:

@SuppressLint("DiscouragedApi")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun CustomControlCard(
    modifier: Modifier,
    navAction: (String) -> Unit,
    controlId: Int,
    packageName: String,
    resourceFolder: Resources,
    animatedIds: MutableSet<Int> // 新增参数:接收已动画项ID集合
) {
    ...
    
    // 根据是否已执行动画设置初始缩放值,避免重复进入时的闪烁
    val scale = remember(controlId, animatedIds) {
        Animatable(if (controlId in animatedIds) 1f else 0f)
    }

    LaunchedEffect(controlId) {
        // 仅当项未执行过动画时,才播放入场动画
        if (controlId !in animatedIds) {
            scale.animateTo(
                targetValue = 1f,
                animationSpec = tween(
                    durationMillis = 250,
                    delayMillis = 100 * controlId,
                    easing = LinearEasing
                )
            )
            // 标记该项已完成动画
            animatedIds.add(controlId)
        }
    }

    Card(
        modifier = modifier
            .size(width = 110.dp, height = 200.dp)
            .scale(scale = scale.value),
        onClick = { }
    ) {
        Box(
            modifier = modifier
                .fillMaxWidth()
                .background(MaterialTheme.colorScheme.surface),
            contentAlignment = Alignment.BottomCenter,
        ) {
            Image(
                painter = painterResource(id = drawableId),
                contentDescription = null,
                alignment = Alignment.Center,
                modifier = Modifier
                    .padding(5.dp)
                    .fillMaxSize()
            )
        }
    }
}

原理说明

  • 父组件的animatedIds集合通过remember绑定页面生命周期,确保页面销毁前状态不会丢失。
  • 每个列表项首次进入组合时,检查自身ID是否在animatedIds中,未存在则执行动画并标记已完成。
  • 当项滚出再滚回时,由于ID已在集合中,动画不会重复触发,同时初始缩放值直接设为1f,避免视觉异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 17:44:59