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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 22:42:46