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

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
                        }
                    }
            )
        }
    }
}

关键细节说明

  1. 避免布局重排:所有item始终保留在LazyRow中,仅通过width和offset动画实现“消失”效果,不会触发item的移除/添加逻辑,彻底解决跳变问题。
  2. 动画同步:宽度与偏移动画使用相同时长的tween动画,保证过渡效果流畅一致。
  3. 交互完整性:添加了点击已展开项恢复初始状态的逻辑,完善用户体验。

扩展优化方向

  • 展开状态下禁用LazyRow的滚动功能,避免误操作
  • 增加缩放、阴影等辅助动画,增强视觉层次感

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 06:33:12