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

Jetpack Compose中LazyRow/LazyColumn居中选中项如何添加缩放动画

LazyRow中心吸附项放大效果实现

效果参考

中心吸附放大滚动效果
当列表项滚动吸附到屏幕中心时自动放大,中心两侧的项恢复原始尺寸,不需要修改现有已实现的无限滚动、snapper吸附逻辑,只需要给列表项增加基于偏移量的缩放动画即可。

实现步骤

  • 提前定义缩放参数:设置中心选中项的最大缩放比例(常用值为1.2f,可按需调整),未选中项保持1f原始缩放比例
  • 监听每个列表项相对于LazyRow容器的位置偏移
  • 根据item中心和屏幕中心的距离计算实时缩放值,搭配Compose动画API做平滑过渡,避免缩放效果生硬

修改后完整代码

@OptIn(ExperimentalSnapperApi::class)
@Composable
fun CircularList(
    data: List<SingleBox>,
    modifier: Modifier = Modifier,
    isEndless: Boolean = true
) {
    val listState = rememberLazyListState(
        if (isEndless) Int.MAX_VALUE / 2 else 0
    )

    val configuration = LocalConfiguration.current
    val itemDimensions = ItemDimensions()
    val screenWidth = configuration.screenWidthDp.dp
    val contentPadding = PaddingValues(start = screenWidth/2 - (itemDimensions.itemWidth/2))
    // 缩放参数配置:中心项放大1.2倍,默认弹簧动效更贴合原生滚动手感
    val maxSelectedScale = 1.2f
    val originScale = 1f
    // 存储LazyRow容器的中心X坐标
    var containerCenterX by remember { mutableFloatStateOf(0f) }

    BoxWithConstraints(
        modifier = modifier.onGloballyPositioned {
            containerCenterX = it.size.width / 2f
        }
    ) {
        LazyRow(
            state = listState,
            modifier = Modifier.fillMaxWidth(),
            contentPadding = contentPadding,
            flingBehavior = rememberSnapperFlingBehavior(
                listState, 
                SnapOffsets.Start, 
                snapIndex = { _, startIndex, targetIndex ->
                    targetIndex.coerceIn(startIndex - 7, startIndex + 7)
                }
            ),
        ) {
            items(
                count = if (isEndless) Int.MAX_VALUE else data.size,
                key = { it }
            ) {
                val index = it % data.size
                // 存储当前item的中心X坐标
                var itemCenterX by remember { mutableFloatStateOf(0f) }
                // 计算当前item与容器中心的偏移比例
                val offsetPercent = remember(itemCenterX, containerCenterX) {
                    if (containerCenterX == 0f || itemCenterX == 0f) 1f
                    else {
                        val distance = kotlin.math.abs(itemCenterX - containerCenterX)
                        // 偏移距离超过单个item宽度时,比例为1,保持原始大小
                        (distance / itemDimensions.itemWidth.toPx()).coerceIn(0f, 1f)
                    }
                }
                // 动画过渡缩放值
                val scale by animateFloatAsState(
                    targetValue = originScale + (maxSelectedScale - originScale) * (1 - offsetPercent),
                    label = "item_scale_anim"
                )

                Box(
                    modifier = Modifier
                        .graphicsLayer {
                            scaleX = scale
                            scaleY = scale
                        }
                        .onGloballyPositioned { layoutCoordinates ->
                            itemCenterX = layoutCoordinates.positionInRoot().x + layoutCoordinates.size.width / 2f
                        }
                ) {
                    CustomItem(data[index])
                }
            }
        }
    }
}

优化说明

  • 缩放逻辑使用graphicsLayer修饰符实现,不会触发全局重组,滚动性能更好
  • 给LazyRow的items增加key参数,避免滚动复用item时位置计算错乱
  • 缩放倍数、动画参数可根据设计稿需求调整,如果需要给非中心项加透明度、位移等效果,也可以复用同样的偏移比例计算对应参数

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 14:36:21