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
相关产品推荐
相关产品推荐

