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

Compose Navigation:为导航目的地设置条件式转场动画

Jetpack Compose Navigation 条件转场动画实现方案

针对你需求的场景——Category屏幕与底部导航目的地间用默认/无动画,与子屏幕间用滑动动画,可以通过以下方式在转场回调中判断导航的来源与目标:

核心思路

利用Compose Navigation提供的转场回调参数(targetDestination、initialState)直接获取导航动作的目标屏幕和来源屏幕信息,无需依赖返回栈的实时状态(哪怕来源屏幕已被弹出)。

具体实现步骤

1. 统一定义路由常量

先把所有屏幕的路由抽成常量,避免拼写错误:

const val HOME_SCREEN = "home"
const val CATEGORY_SCREEN = "category"
const val CATEGORY_DETAIL_SCREEN = "category_detail"

2. 在NavHost中配置条件转场

在composable函数的转场参数中,通过targetDestination(目标屏幕)和initialState(导航前的栈条目,即来源屏幕)做判断:

val navController = rememberNavController()

NavHost(
    navController = navController,
    startDestination = HOME_SCREEN
) {
    // 首页(带底部导航)
    composable(HOME_SCREEN) {
        HomeScreen(onNavigateToCategory = {
            // 底部导航跳转Category时,保持栈的正确状态
            navController.navigate(CATEGORY_SCREEN) {
                popUpTo(navController.graph.startDestinationId) {
                    saveState = true
                }
                launchSingleTop = true
                restoreState = true
            }
        })
    }

    // Category屏幕
    composable(
        route = CATEGORY_SCREEN,
        // 退出动画:只有前往子屏幕时用滑动
        exitTransition = {
            if (targetDestination.route == CATEGORY_DETAIL_SCREEN) {
                slideOutHorizontally(targetOffsetX = { -it }) // 向左滑出
            } else {
                fadeOut() // 返回底部导航时用淡出(或用null表示无动画)
            }
        },
        // 回退进入动画:只有从子屏幕返回时用滑动
        popEnterTransition = {
            if (initialState.destination.route == CATEGORY_DETAIL_SCREEN) {
                slideInHorizontally(initialOffsetX = { -it }) // 向左滑入
            } else {
                fadeIn() // 从底部导航进入时用淡入
            }
        }
    ) {
        CategoryScreen(onItemClick = { itemId ->
            navController.navigate("$CATEGORY_DETAIL_SCREEN/$itemId")
        })
    }

    // Category子屏幕(详情页)
    composable(
        route = "$CATEGORY_DETAIL_SCREEN/{itemId}",
        arguments = listOf(navArgument("itemId") { type = NavType.StringType }),
        // 进入动画:只有从Category进入时用滑动
        enterTransition = {
            if (initialState.destination.route == CATEGORY_SCREEN) {
                slideInHorizontally(initialOffsetX = { it }) // 向右滑入
            } else {
                fadeIn()
            }
        },
        // 回退退出动画:只有返回Category时用滑动
        popExitTransition = {
            if (targetDestination.route == CATEGORY_SCREEN) {
                slideOutHorizontally(targetOffsetX = { it }) // 向右滑出
            } else {
                fadeOut()
            }
        }
    ) { backStackEntry ->
        val itemId = backStackEntry.arguments?.getString("itemId") ?: ""
        CategoryDetailScreen(onBackClick = { navController.popBackStack() })
    }
}

关键参数说明

  • targetDestination:在exitTransition和popExitTransition中可用,直接指向导航动作的目标屏幕路由,用来判断要去的是哪个页面。
  • initialState:在enterTransition和popEnterTransition中可用,代表导航动作发生前的栈条目,哪怕来源屏幕已经被弹出,依然能通过它获取来源屏幕的路由,完美解决你提到的“前序屏幕已弹出无法检查返回栈”的问题。

优化建议

如果有多个子屏幕或者复杂的判断逻辑,可以把判断逻辑抽成扩展函数,让代码更简洁:

private fun NavBackStackEntry?.isCategoryScreen() = this?.destination?.route == CATEGORY_SCREEN
private fun NavDestination?.isCategoryDetailScreen() = this?.route?.startsWith(CATEGORY_DETAIL_SCREEN) == true

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 05:05:12