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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 23:21:04