如何在Compose Scaffold中实现独立可折叠元素(非折叠顶部栏)
实现FilterChip行的滚动收起/显示效果
问题描述
需求:向上滚动LazyGrid时,FilterChip行自动收起;向下滚动时重新显示,实现类似
TopAppBarDefaults.enterAlwaysScrollBehavior()的滚动行为。Chip行属于内容区而非顶部应用栏,要求顶部应用栏始终保持可见。当前使用Material3 + Compose 1.4.1,已为顶部栏设置TopAppBarDefaults.pinnedScrollBehavior()但未达到预期效果,单独给Chip行加滚动状态也无效。
解决方案
核心思路是将FilterChip行的显示状态与LazyGrid的滚动状态绑定,通过监听滚动方向实现平滑的收起/显示动画,同时保持顶部应用栏固定可见。
步骤1:修改TvShowsSection,暴露LazyListState
首先需要让LazyGrid的滚动状态可以被外部监听,修改TvShowsSection使其接收并使用传入的LazyListState:
@Composable fun TvShowsSection( tvShows: LazyPagingItems<TvShow>, onShowClick: (TvShow) -> Unit, modifier: Modifier = Modifier, listState: LazyListState = rememberLazyListState() ) { LazyVerticalGrid( state = listState, columns = GridCells.Fixed(2), modifier = modifier, contentPadding = PaddingValues(4.dp) ) { // 原有LazyGrid内容逻辑保持不变 items(tvShows) { show -> show?.let { TvShowItem( tvShow = it, onClick = { onShowClick(it) }, modifier = Modifier.padding(4.dp) ) } } } }
步骤2:在HomeScreen中实现滚动监听与动画
在HomeScreen中创建LazyGrid的滚动状态,监听滚动方向,通过动画偏移实现Chip行的收起/显示:
@Composable fun HomeScreen( state: HomeState, tvShows: LazyPagingItems<TvShow>, onEvent: (HomeEvent) -> Unit, onNavigateToTvShow: (TvShow) -> Unit, onNavigateToProfile: () -> Unit ) { // 顶部栏使用pinned滚动行为,保持始终可见 val topBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() // LazyGrid的滚动状态 val listState = rememberLazyListState() // 记录上一次滚动位置,用于判断滚动方向 val lastScrollPosition = remember { mutableStateOf(0) } // 保存Chip行的实际高度 val chipHeight = remember { mutableStateOf(0.dp) } // Chip行的动画偏移量:向上滚动时隐藏,向下滚动时显示 val chipOffset by animateDpAsState( targetValue = if (listState.firstVisibleItemScrollOffset > 0 && listState.firstVisibleItemScrollPosition > lastScrollPosition.value ) { -chipHeight.value } else { 0.dp }, animationSpec = tween(durationMillis = 200) ) // 监听滚动位置变化,更新上一次滚动位置 LaunchedEffect(listState) { snapshotFlow { listState.firstVisibleItemScrollPosition } .collect { position -> lastScrollPosition.value = position } } Scaffold( topBar = { HomeTopAppBar( onProfileClick = onNavigateToProfile, scrollBehavior = topBarScrollBehavior ) } ) { padding -> Column(modifier = Modifier.padding(padding)) { ChipsSection( selectedFilter = state.showFilter, onFilterClick = { onEvent(HomeEvent.OnShowFilterClick(it)) }, modifier = Modifier .fillMaxWidth() .padding(8.dp) // 获取Chip行的实际高度 .onSizeChanged { size -> chipHeight.value = size.height.toDp() } // 应用动画偏移 .offset(y = chipOffset) ) TvShowsSection( tvShows = tvShows, onShowClick = onNavigateToTvShow, modifier = Modifier .fillMaxSize() .padding(8.dp), listState = listState ) } } }
关键逻辑说明
- 滚动方向判断:通过对比
listState.firstVisibleItemScrollPosition的当前值和上一次值,判断用户是向上还是向下滚动。 - 平滑动画:使用
animateDpAsState实现Chip行的垂直偏移动画,收起时向上偏移自身高度,显示时恢复原位,过渡自然。 - 高度获取:通过
onSizeChanged监听Chip行的实际渲染高度,确保偏移量准确适配不同屏幕。
内容的提问来源于stack exchange,提问作者Gumu96
相关产品推荐
相关产品推荐

