如何在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
相关产品推荐
相关产品推荐

