如何在内容无需滚动时禁止TopAppBar的滚动行为
解决TopAppBar在内容无需滚动时的折叠/展开问题
核心思路是仅当内容超出可视区域、需要滚动时,才启用TopAppBar的滚动行为。以下分两种场景给出实现方案:
场景1:使用LazyColumn的情况
借助LazyListState的布局信息,判断列表内容总高度是否超过可视区域高度,动态控制TopAppBar滚动行为的启用状态:
@Composable fun ScrollableTopAppBarWithLazyColumn() { val scrollBehavior = remember { TopAppBarDefaults.enterAlwaysScrollBehavior() } val lazyListState = rememberLazyListState() // 判断内容是否可滚动 val isContentScrollable by remember { derivedStateOf { val layoutInfo = lazyListState.layoutInfo val totalItems = layoutInfo.totalItemsCount if (totalItems == 0) return@derivedStateOf false // 计算所有内容的总高度(包含不可见部分) val totalContentHeight = layoutInfo.visibleItemsInfo.sumOf { it.size } + layoutInfo.viewportStartOffset + layoutInfo.viewportEndOffset // 总高度大于可视区域高度则判定为可滚动 totalContentHeight > layoutInfo.viewportSize.height } } // 根据滚动状态更新TopAppBar滚动行为的启用状态 LaunchedEffect(isContentScrollable) { scrollBehavior.isEnabled = isContentScrollable } Scaffold( // 仅内容可滚动时才添加嵌套滚动连接 modifier = if (isContentScrollable) Modifier.nestedScroll(scrollBehavior.nestedScrollConnection) else Modifier, topBar = { TopAppBar( title = { Text("TopAppBar", maxLines = 1, overflow = TextOverflow.Ellipsis) }, navigationIcon = { IconButton(onClick = { /* 点击逻辑 */ }) { Icon(Icons.Filled.Menu, contentDescription = "菜单") } }, actions = { IconButton(onClick = { /* 点击逻辑 */ }) { Icon(Icons.Filled.Favorite, contentDescription = "收藏") } IconButton(onClick = { /* 点击逻辑 */ }) { Icon(Icons.Filled.Favorite, contentDescription = "收藏") } }, scrollBehavior = scrollBehavior ) }, content = { innerPadding -> LazyColumn( state = lazyListState, contentPadding = innerPadding, verticalArrangement = Arrangement.spacedBy(8.dp) ) { val list = (0..5).map { it.toString() } items(count = list.size) { Text( text = list[it], style = MaterialTheme.typography.bodyLarge, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp) ) } } } ) }
场景2:使用Column+verticalScroll的情况
普通Column没有内置的滚动状态信息,需要通过onSizeChanged分别获取父容器高度和内容高度,对比后判断是否需要启用TopAppBar滚动行为:
@Composable fun ScrollableTopAppBarWithColumn() { val scrollBehavior = remember { TopAppBarDefaults.enterAlwaysScrollBehavior() } val scrollState = rememberScrollState() var parentContainerHeight by remember { mutableStateOf(0) } var columnContentHeight by remember { mutableStateOf(0) } // 判断内容是否可滚动 val isContentScrollable by remember { derivedStateOf { columnContentHeight > parentContainerHeight } } // 根据滚动状态更新TopAppBar滚动行为的启用状态 LaunchedEffect(isContentScrollable) { scrollBehavior.isEnabled = isContentScrollable } Scaffold( // 仅内容可滚动时才添加嵌套滚动连接 modifier = if (isContentScrollable) Modifier.nestedScroll(scrollBehavior.nestedScrollConnection) else Modifier, topBar = { TopAppBar( title = { Text("TopAppBar", maxLines = 1, overflow = TextOverflow.Ellipsis) }, navigationIcon = { IconButton(onClick = { /* 点击逻辑 */ }) { Icon(Icons.Filled.Menu, contentDescription = "菜单") } }, actions = { IconButton(onClick = { /* 点击逻辑 */ }) { Icon(Icons.Filled.Favorite, contentDescription = "收藏") } IconButton(onClick = { /* 点击逻辑 */ }) { Icon(Icons.Filled.Favorite, contentDescription = "收藏") } }, scrollBehavior = scrollBehavior ) }, content = { innerPadding -> Box( modifier = Modifier .fillMaxSize() .padding(innerPadding) // 获取父容器的可用高度 .onSizeChanged { parentContainerHeight = it.height } ) { Column( modifier = Modifier .verticalScroll(scrollState) // 获取Column内容的总高度 .onSizeChanged { columnContentHeight = it.height }, verticalArrangement = Arrangement.spacedBy(8.dp) ) { val list = (0..5).map { it.toString() } list.forEach { Text( text = it, style = MaterialTheme.typography.bodyLarge, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp) ) } } } } ) }
关键说明
- 使用
derivedStateOf自动计算滚动状态,避免不必要的重组 - 通过
scrollBehavior.isEnabled控制TopAppBar是否响应滚动操作 - 仅当内容可滚动时,才给Scaffold添加
nestedScroll修饰符,避免无意义的滚动事件传递
内容的提问来源于stack exchange,提问作者Nik
相关产品推荐
相关产品推荐

