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

Android Compose实现TopAppBar与TabBar联动折叠的方法

要实现TopAppBar和TabBar联动折叠,核心是将TabBar纳入Scaffold的topBar区域,并通过同一滚动状态控制两者的动画。以下是两种解决方案:


方案一:TabBar随TopAppBar同步淡入淡出+小幅度上移

此方案保留TopAppBar的默认折叠行为,同时让TabBar跟随其同步移动并逐渐消失:

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NestedScrollTest() {
    val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState())
    val scrollState = scrollBehavior.state
    var selectedTabIndex by remember { mutableStateOf(0) }
    val titles = listOf("Tab 1", "Tab 2", "Tab 3 with lots of text")

    Scaffold(
        modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
        topBar = {
            Column {
                TopAppBar(
                    title = { Text(text = "Scroll Behavior Test") },
                    navigationIcon = {
                        IconButton(onClick = { /*TODO*/ }) {
                            Icon(imageVector = Icons.Default.Menu, contentDescription = "Menu")
                        }
                    },
                    scrollBehavior = scrollBehavior
                )

                TabRow(
                    modifier = Modifier
                        // 随TopAppBar折叠同步上移
                        .translationY(scrollState.offset)
                        // 随折叠进度逐渐淡出
                        .alpha(1 + (scrollState.offset / scrollState.maxScrollOffset)),
                    selectedTabIndex = selectedTabIndex,
                    containerColor = MaterialTheme.colorScheme.surface
                ) {
                    titles.forEachIndexed { index, title ->
                        Tab(
                            selected = selectedTabIndex == index,
                            onClick = { selectedTabIndex = index },
                            text = { Text(text = title, maxLines = 2, overflow = TextOverflow.Ellipsis) }
                        )
                    }
                }
            }
        }
    ) { innerPadding ->
        LazyColumn(
            modifier = Modifier.fillMaxWidth(),
            contentPadding = PaddingValues(
                // 动态调整内容顶部padding,匹配折叠后的导航栏高度
                top = innerPadding.calculateTopPadding() + scrollState.offset.toDp(),
                start = innerPadding.calculateStartPadding(LocalLayoutDirection.current),
                end = innerPadding.calculateEndPadding(LocalLayoutDirection.current),
                bottom = innerPadding.calculateBottomPadding()
            )
        ) {
            items((1..50).toList()) { item ->
                Text(modifier = Modifier.padding(8.dp), text = "Item $item")
            }
        }
    }
}

方案二:完全折叠TabBar,仅保留最小化TopAppBar

此方案让TabBar完全滑出屏幕,最终仅显示折叠后的TopAppBar:

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NestedScrollTest() {
    val expandedTopBarHeight = 64.dp
    val collapsedTopBarHeight = 56.dp
    val tabBarHeight = 56.dp
    val totalExpandedHeight = expandedTopBarHeight + tabBarHeight

    val density = LocalDensity.current
    val expandedTopBarPx = density.run { expandedTopBarHeight.toPx() }
    val collapsedTopBarPx = density.run { collapsedTopBarHeight.toPx() }
    val tabBarPx = density.run { tabBarHeight.toPx() }
    val totalExpandedPx = expandedTopBarPx + tabBarPx

    val scrollOffset = remember { mutableStateOf(0f) }

    val nestedScrollConnection = remember {
        object : NestedScrollConnection {
            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
                val delta = available.y
                // 限制滚动范围:从完全展开到仅显示折叠后的TopAppBar
                scrollOffset.value = (scrollOffset.value + delta).coerceIn(-(totalExpandedPx - collapsedTopBarPx), 0f)
                return Offset.Zero
            }
        }
    }

    var selectedTabIndex by remember { mutableStateOf(0) }
    val titles = listOf("Tab 1", "Tab 2", "Tab 3 with lots of text")

    Scaffold(
        modifier = Modifier.nestedScroll(nestedScrollConnection),
        topBar = {
            Box(
                modifier = Modifier
                    .fillMaxWidth()
                    .height(totalExpandedHeight)
            ) {
                // 可折叠的导航栏内容:TopAppBar + TabBar
                Column(
                    modifier = Modifier
                        .fillMaxWidth()
                        .translationY(scrollOffset.value)
                ) {
                    // 动态调整TopAppBar高度,从展开到折叠
                    TopAppBar(
                        modifier = Modifier.height(
                            density.run {
                                val heightDelta = expandedTopBarPx - collapsedTopBarPx
                                val barHeight = expandedTopBarPx + scrollOffset.value.coerceIn(-heightDelta, 0f)
                                barHeight.toDp()
                            }
                        ),
                        title = { Text(text = "Scroll Behavior Test") },
                        navigationIcon = {
                            IconButton(onClick = { /*TODO*/ }) {
                                Icon(imageVector = Icons.Default.Menu, contentDescription = "Menu")
                            }
                        }
                    )

                    TabRow(
                        selectedTabIndex = selectedTabIndex,
                        containerColor = MaterialTheme.colorScheme.surface
                    ) {
                        titles.forEachIndexed { index, title ->
                            Tab(
                                selected = selectedTabIndex == index,
                                onClick = { selectedTabIndex = index },
                                text = { Text(text = title, maxLines = 2, overflow = TextOverflow.Ellipsis) }
                            )
                        }
                    }
                }

                // 固定显示的折叠后TopAppBar(完全折叠时可见)
                if (scrollOffset.value <= -(expandedTopBarPx - collapsedTopBarPx)) {
                    TopAppBar(
                        modifier = Modifier.height(collapsedTopBarHeight),
                        title = { Text(text = "Scroll Behavior Test") },
                        navigationIcon = {
                            IconButton(onClick = { /*TODO*/ }) {
                                Icon(imageVector = Icons.Default.Menu, contentDescription = "Menu")
                            }
                        }
                    )
                }
            }
        }
    ) { innerPadding ->
        LazyColumn(
            modifier = Modifier.fillMaxWidth(),
            contentPadding = PaddingValues(
                top = innerPadding.calculateTopPadding() + density.run { scrollOffset.value.toDp() },
                start = innerPadding.calculateStartPadding(LocalLayoutDirection.current),
                end = innerPadding.calculateEndPadding(LocalLayoutDirection.current),
                bottom = innerPadding.calculateBottomPadding()
            )
        ) {
            items((1..50).toList()) { item ->
                Text(modifier = Modifier.padding(8.dp), text = "Item $item")
            }
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 10:57:31