Jetpack Compose使用Accompanist导航动画时隐藏底部栏跳变如何解决
问题根因
你遇到的布局跳动问题核心原因是:currentRoute 会在导航动作触发时立刻更新为目标路由(详情页),此时 Scaffold 的 bottomBar 会直接移除,innerPadding 同步缩小,主页面底部会先被拉伸重排,之后才会执行 AnimatedNavHost 定义的转场动画,二者时序差导致了视觉割裂。
另外你现有代码里还有个笔误:判断显示 bottomBar 的条件写反了,当前逻辑是路由为详情页时才展示底部导航,和需求刚好相反。
解决方案推荐
方案1:底部导航加同步转场动画(改动最小)
废弃直接 if-else 控制显隐的逻辑,用 AnimatedVisibility 包裹底部导航,让它的退场动画和页面转场动画时长、效果对齐,同时对 innerPadding 做同步过渡,避免布局跳动:
setContent { val navController = rememberAnimatedNavController() val navBackStackEntry by navController.currentBackStackEntryAsState() val currentRoute = navBackStackEntry?.destination?.route?.substringBeforeLast("/") // 定义和页面转场一致的动画时长,可根据自己的需求调整 val animationSpec = tween<Float>(durationMillis = 300) // 判断是否需要展示底部导航:仅主页面展示 val showBottomBar = currentRoute in listOf(Screen.Dashboard.route, Screen.Map.route, Screen.Events.route) BottomBarNavTestTheme { Scaffold( bottomBar = { AnimatedVisibility( visible = showBottomBar, exit = fadeOut(animationSpec) + slideOutVertically(animationSpec) { it }, // 和页面退场同步渐隐+底部滑出 enter = fadeIn(animationSpec) + slideInVertically(animationSpec) { it } ) { MyBottomNavigation( navController, currentRoute, listOf(Screen.Dashboard, Screen.Map, Screen.Events) ) } } ) { innerPadding -> // 对底部padding做动画过渡,避免布局突然跳动 val animatedBottomPadding by animateDpAsState( targetValue = if (showBottomBar) innerPadding.calculateBottomPadding() else 0.dp, animationSpec = animationSpec, label = "bottomPaddingAnim" ) AnimatedNavHost( navController = navController, startDestination = Screen.Dashboard.route, modifier = Modifier.padding( top = innerPadding.calculateTopPadding(), bottom = animatedBottomPadding ) ) { composable(Screen.Dashboard.route) { DashboardScreen() } composable(Screen.Map.route) { MapScreen { navController.navigate(Screen.Detail.route) } } composable(Screen.Events.route) { EventsScreen() } composable( Screen.Detail.route, enterTransition = { fadeIn(animationSpec) }, exitTransition = { fadeOut(animationSpec) } ) { MapDetailScreen() } } } } }
方案2:自定义布局层级(更灵活,无布局抖动风险)
如果不想处理padding的同步动画,可以直接废弃 Scaffold 的 bottomBar 参数,自己用 Box 做层级嵌套,让底部导航直接悬浮在 AnimatedNavHost 上方,仅给主页面加固定的底部避让padding,详情页不加,完全规避布局重排问题:
setContent { val navController = rememberAnimatedNavController() val navBackStackEntry by navController.currentBackStackEntryAsState() val currentRoute = navBackStackEntry?.destination?.route?.substringBeforeLast("/") val showBottomBar = currentRoute in listOf(Screen.Dashboard.route, Screen.Map.route, Screen.Events.route) // 标准Material Design底部导航高度,可根据自己的组件实际高度调整 val bottomBarHeight = 56.dp val animationSpec = tween<Float>(durationMillis = 300) BottomBarNavTestTheme { Box(modifier = Modifier.fillMaxSize()) { // 导航容器直接占满全屏,不需要处理Scaffold的padding AnimatedNavHost( navController = navController, startDestination = Screen.Dashboard.route, modifier = Modifier.fillMaxSize() ) { // 主页面单独加底部padding避让导航栏 composable(Screen.Dashboard.route) { DashboardScreen(modifier = Modifier.padding(bottom = bottomBarHeight)) } composable(Screen.Map.route) { MapScreen( modifier = Modifier.padding(bottom = bottomBarHeight), onJumpDetail = { navController.navigate(Screen.Detail.route) } ) } composable(Screen.Events.route) { EventsScreen(modifier = Modifier.padding(bottom = bottomBarHeight)) } // 详情页不加padding,占满全屏 composable(Screen.Detail.route) { MapDetailScreen() } } // 底部导航悬浮在最上层,做显隐动画 AnimatedVisibility( visible = showBottomBar, modifier = Modifier.align(Alignment.BottomCenter), exit = fadeOut(animationSpec) + slideOutVertically(animationSpec) { it }, enter = fadeIn(animationSpec) + slideInVertically(animationSpec) { it } ) { MyBottomNavigation( navController, currentRoute, listOf(Screen.Dashboard, Screen.Map, Screen.Events) ) } } } }
两种方案都可以实现期望效果:主页面带着底部导航一起渐隐退场,详情页无底部导航直接入场,不会出现中间的布局跳动。
内容的提问来源于stack exchange,提问作者m.reiter
相关产品推荐
相关产品推荐

