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

Jetpack Compose LazyColumn 实现带动画的滑动删除并解决滚动卡顿问题

问题根因

你遇到的滚动卡顿、上滑跳顶部的问题由以下几个错误实现导致:

  • 每个列表项内部嵌套AnimatedVisibility自行控制显隐,会破坏LazyColumn的布局测量逻辑,滚动时条目位置计算错乱,是跳转顶部的核心诱因
  • rememberDismissState、unread等状态没有绑定条目唯一标识,列表滚动条目复用时状态会发生串位,引发卡顿和状态异常
  • 全局columnAppeared变量控制入场动画的逻辑错误,新条目滚动进可视区域时会触发不必要的重绘
正确实现方案

Jetpack Compose 已经提供了原生的列表条目位移动画支持animateItemPlacement,效果和原生RecyclerView搭配DiffUtil的插入/移除动画完全一致,不需要手动嵌套AnimatedVisibility控制显隐,修改后的代码如下:

// UI层先收集ViewModel中的列表数据,确保用生命周期感知的方式收集
val products by viewModel.products.collectAsStateWithLifecycle()

LazyColumn(state = listState) {
    items(
        items = products,
        key = { listItem: InventoryEntity -> listItem.inventoryId }
    ) { item ->
        // 状态绑定item的唯一id,避免复用错乱
        var unread by remember(item.inventoryId) { mutableStateOf(false) }
        val dismissState = rememberDismissState(
            key1 = item.inventoryId,
            confirmStateChange = {
                if (it == DismissValue.DismissedToEnd) unread = !unread
                it != DismissValue.DismissedToEnd
            }
        )
        val isDismissed = dismissState.isDismissed(DismissDirection.EndToStart)

        if (isDismissed) {
            LaunchedEffect(item.inventoryId) {
                // 等待动画执行完成再删除数据,和动画时长保持一致
                delay(300)
                viewModel.deleteProduct(item.inventoryId)
            }
        }

        SwipeToDismiss(
            // 加animateItemPlacement实现条目插入/移除的平滑位移动画
            modifier = Modifier
                .padding(vertical = 4.dp)
                .animateItemPlacement(
                    animationSpec = tween(durationMillis = 300)
                ),
            state = dismissState,
            directions = setOf(
                DismissDirection.StartToEnd,
                DismissDirection.EndToStart
            ),
            dismissThresholds = { direction ->
                FractionalThreshold(if (direction == DismissDirection.StartToEnd) 0.25f else 0.5f)
            },
            background = {
                val direction = dismissState.dismissDirection ?: return@SwipeToDismiss
                val color by animateColorAsState(
                    when (dismissState.targetValue) {
                        DismissValue.Default -> Color.LightGray
                        DismissValue.DismissedToEnd -> Color.Green
                        DismissValue.DismissedToStart -> Color.Red
                    }, label = "swipeBgColor"
                )
                val alignment = when (direction) {
                    DismissDirection.StartToEnd -> Alignment.CenterStart
                    DismissDirection.EndToStart -> Alignment.CenterEnd
                }
                val icon = when (direction) {
                    DismissDirection.StartToEnd -> Icons.Default.Done
                    DismissDirection.EndToStart -> Icons.Default.Delete
                }
                val scale by animateFloatAsState(
                    if (dismissState.targetValue == DismissValue.Default) 0.75f else 1f, label = "swipeIconScale"
                )

                Box(
                    Modifier
                        .fillMaxSize()
                        .background(color)
                        .padding(horizontal = 20.dp),
                    contentAlignment = alignment
                ) {
                    Icon(
                        icon,
                        contentDescription = "Localized description",
                        modifier = Modifier.scale(scale)
                    )
                }
            },
            dismissContent = {
                Card(
                    elevation = animateDpAsState(
                        if (dismissState.dismissDirection != null) 4.dp else 0.dp, label = "cardElevation"
                    ).value
                ) {
                    ProductRow(product = item, number = item.inventoryId)
                }
            }
        )
    }
}

同时你需要在ViewModel层维护products状态,删除操作直接更新products对应的状态流即可,Compose的items函数传入key之后会自动做Diff计算,和原生DiffUtil效果一致。

额外优化建议
  • 给InventoryEntity类添加@Immutable注解,确保所有字段都是不可变的val类型,提升Compose重绘判断效率,滚动会更流畅
  • 所有动画的label参数可以按实际业务命名,方便调试,release包会自动移除不会影响性能
  • 避免在LazyColumn的items作用域内创建未绑定条目key的状态,防止条目复用时出现状态异常

内容的提问来源于stack exchange,提问作者F.Mysir

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 00:45:06