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

如何在Android Compose的LazyGrid中实现选中项动画效果

Android Compose 网格选中动画优化方案

你当前的实现用固定偏移量控制选中边框位置,但LazyVerticalGrid是滚动组件,拖动时选中框偏移不会跟随列表滚动,导致位置错位,这是核心Bug。下面提供两种可行的优化思路:

思路一:选中边框与列表项绑定(推荐)

让每个列表项自身管理选中状态,选中时显示边框动画,完全跟随列表滚动,不会出现错位,代码更简洁。

fun GridViewOptimized() {
    var selectedIndex by remember { mutableStateOf(0) }

    LazyVerticalGrid(
        modifier = Modifier.fillMaxSize(),
        columns = GridCells.Adaptive(200.dp),
        contentPadding = PaddingValues(0.dp)
    ) {
        items(count = 20) { index ->
            val isSelected = index == selectedIndex
            // 动画过渡选中状态的边框颜色和宽度
            val borderColor by animateColorAsState(
                targetValue = if (isSelected) Color.Green else Color.Transparent,
                label = "borderColor"
            )
            val borderWidth by animateDpAsState(
                targetValue = if (isSelected) 2.dp else 0.dp,
                label = "borderWidth"
            )

            Box(
                modifier = Modifier
                    .width(200.dp)
                    .height(150.dp)
                    .clickable { selectedIndex = index }
                    .padding(3.5.dp) // 对应原方案中外框与内容的间距
                    .border(borderWidth, borderColor)
                    .padding(3.5.dp),
                contentAlignment = Alignment.Center
            ) {
                Box(
                    modifier = Modifier
                        .width(190.dp)
                        .height(140.dp)
                        .background(Color.Gray)
                )
            }
        }
    }
}

思路二:动态获取选中项位置(保留全局选中框)

如果需要保留单独的全局选中框(比如跨项平滑移动动画),可以通过监听选中项的全局位置和列表滚动偏移,动态更新选中框的位置:

fun GridViewWithGlobalSelection() {
    var selectedIndex by remember { mutableStateOf(0) }
    var selectedItemPosition by remember { mutableStateOf(Offset.Zero) }
    var gridScrollOffset by remember { mutableStateOf(0f) }
    // 动画过渡选中框位置
    val animatedOffset by animateOffsetAsState(
        targetValue = Offset(selectedItemPosition.x, selectedItemPosition.y - gridScrollOffset),
        label = "selectionOffset"
    )

    Box(modifier = Modifier.fillMaxSize()) {
        val gridState = rememberLazyGridState()
        // 监听列表滚动偏移,实时修正选中框位置
        LaunchedEffect(gridState) {
            snapshotFlow { gridState.firstVisibleItemScrollOffset }
                .collect { offset ->
                    gridScrollOffset = offset.toFloat()
                }
        }

        LazyVerticalGrid(
            modifier = Modifier.fillMaxSize(),
            columns = GridCells.Adaptive(200.dp),
            contentPadding = PaddingValues(0.dp),
            state = gridState
        ) {
            items(count = 20) { index ->
                Box(
                    modifier = Modifier
                        .width(200.dp)
                        .height(150.dp)
                        .clickable { selectedIndex = index }
                        // 获取选中项的全局坐标
                        .onGloballyPositioned { coordinates ->
                            if (index == selectedIndex) {
                                selectedItemPosition = coordinates.localToGlobal(Offset.Zero)
                            }
                        },
                    contentAlignment = Alignment.Center
                ) {
                    Box(
                        modifier = Modifier
                            .width(190.dp)
                            .height(140.dp)
                            .background(Color.Gray)
                    )
                }
            }
        }

        // 全局选中框
        Box(
            modifier = Modifier
                .width(207.dp)
                .height(150.dp)
                .offset(x = Dp(animatedOffset.x), y = Dp(animatedOffset.y))
                .padding(3.5.dp),
            contentAlignment = Alignment.Center
        ) {
            Box(
                modifier = Modifier
                    .width(190.dp)
                    .height(140.dp)
                    .border(2.dp, Color.Green)
            )
        }
    }
}

优化说明

  • 原方案Bug根源:固定计算的偏移量没有考虑列表滚动,拖动时选中框无法跟随滚动,导致位置错位。
  • 思路一优势:复用LazyGrid的滚动机制,无需额外处理偏移,动画稳定,代码简洁,适合大多数场景。
  • 思路二适用场景:需要选中框有跨项平滑移动过渡动画时,通过监听项位置和滚动偏移,保证选中框位置始终准确。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 13:01:24