Jetpack Compose实现Lazy Row点击盒子展开及平滑动画优化问题
解决方案
问题根源是AnimatedVisibility会直接从LazyRow中移除非选中项,触发LazyRow重新布局,导致剩余的选中项被推到起始位置产生跳变。要解决这个问题,我们需要保留所有item在布局中,通过动画修改尺寸和偏移实现消失效果,同时让选中项平滑展开至全屏。
核心实现逻辑
- 用状态管理选中项索引与展开状态
- 对每个item,根据选中状态和展开状态,动画修改宽度与偏移量
- 选中项动画展开至屏幕宽度,非选中项动画收缩至0宽度并偏移出屏幕范围
完整代码示例
import androidx.compose.animation.animateDpAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp data class BoxItem(val color: Color, val id: Int) @Composable fun ExpandableLazyRow() { val screenWidth = LocalConfiguration.current.screenWidthDp.dp val items = listOf( BoxItem(Color.Red, 1), BoxItem(Color.Green, 2), BoxItem(Color.Blue, 3) ) var selectedIndex by remember { mutableIntStateOf(-1) } var isExpanded by remember { mutableStateOf(false) } LazyRow(modifier = Modifier.fillMaxWidth().height(200.dp)) { itemsIndexed(items) { index, item -> val isSelected = index == selectedIndex // 动画计算宽度:展开状态下选中项占满屏幕,非选中项宽度收至0;未展开时保持固定宽度 val targetWidth = if (isExpanded) { if (isSelected) screenWidth else 0.dp } else { 150.dp } val width by animateDpAsState( targetValue = targetWidth, animationSpec = tween(500), label = "widthAnimation" ) // 动画计算偏移:展开状态下非选中项根据位置偏移出屏幕,选中项保持原位 val targetOffset: Dp = when { !isExpanded -> 0.dp isSelected -> 0.dp index < selectedIndex -> -screenWidth // 左侧项向左偏移 else -> screenWidth // 右侧项向右偏移 } val offsetX by animateDpAsState( targetValue = targetOffset, animationSpec = tween(500), label = "offsetAnimation" ) Box( modifier = Modifier .width(width) .height(200.dp) .offset(x = offsetX) .background(item.color) .clickable { if (isSelected && isExpanded) { // 点击已展开项恢复初始状态 isExpanded = false selectedIndex = -1 } else { selectedIndex = index isExpanded = true } } ) } } }
关键细节说明
- 避免布局重排:所有item始终保留在LazyRow中,仅通过
width和offset动画实现“消失”效果,不会触发item的移除/添加逻辑,彻底解决跳变问题。 - 动画同步:宽度与偏移动画使用相同时长的
tween动画,保证过渡效果流畅一致。 - 交互完整性:添加了点击已展开项恢复初始状态的逻辑,完善用户体验。
扩展优化方向
- 展开状态下禁用LazyRow的滚动功能,避免误操作
- 增加缩放、阴影等辅助动画,增强视觉层次感
内容的提问来源于stack exchange,提问作者Heleena Joy
相关产品推荐
相关产品推荐

