Compose Navigation 3中如何获取AnimatedContentScope?
问题描述
Jetpack Navigation 3已稳定,迁移后在实现共享元素动画时遇到问题:需要获取AnimatedContentScope,但在EntryProviderScope<NavKey>作用域中直接传递this会出现类型不匹配错误,提示实际类型为EntryProviderScope,但期望AnimatedContentScope。
依赖配置(libs.versions.toml)
[versions] navigation2 = "2.9.6" navigation3 = "1.0.0" # 其他库版本 [libraries] androidx-navigation2 = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation2" } androidx-navigation3-runtime = { module = "androidx.navigation3:navigation3-runtime", version.ref = "navigation3" } androidx-navigation3-ui = { module = "androidx.navigation3:navigation3-ui", version.ref = "navigation3" } # 其他库 [plugins] # 插件配置
Navigation 2 可正常运行的代码
AppNavHost.kt
@OptIn(ExperimentalSharedTransitionApi::class) @Composable fun AppNavHost() { SharedTransitionLayout { val navController = rememberNavController() NavHost( navController = navController, startDestination = DashboardNavRoute, ) { dashboard( navController = navController, sharedTransitionScope = this@SharedTransitionLayout ) // 其他路由 } } }
DashboardRoot.kt
@OptIn(ExperimentalSharedTransitionApi::class) fun NavGraphBuilder.dashboard( navController: NavController, sharedTransitionScope: SharedTransitionScope, ) { composable<DashboardNavRoute> { val viewModel = hiltViewModel<DashBoardViewModel>() val state by viewModel.uiState.collectAsState() val snackbarHostState = remember { SnackbarHostState() } // 其他组件 DashboardScreen( onEvent = viewModel::onEvent, state = state, snackbarHostState = snackbarHostState, sharedTransitionScope = sharedTransitionScope, animatedContentScope = this, ) } }
DashboardScreen.kt
@OptIn(ExperimentalMaterial3Api::class, ExperimentalSharedTransitionApi::class) @Composable internal fun DashboardScreen( snackbarHostState: SnackbarHostState, sharedTransitionScope: SharedTransitionScope, animatedContentScope: AnimatedContentScope, onEvent: (DashboardUiEvent) -> Unit, state: DashboardUiState, ) { // 页面内容 }
Navigation 3 迁移后的代码
AppNavDisplay.kt
@OptIn(ExperimentalSharedTransitionApi::class) @Composable fun AppNavDisplay() { SharedTransitionLayout { val backStack = rememberNavBackStack(DashboardNavRoute) NavDisplay( backStack = backStack, entryProvider = entryProvider { dashboardEntry( backStack = backStack, sharedTransitionScope = this@SharedTransitionLayout ) // 其他条目 } ) } }
DashboardRoot.kt(存在错误的代码)
@OptIn(ExperimentalSharedTransitionApi::class) fun EntryProviderScope<NavKey>.dashboardEntry( backStack : NavBackStack<NavKey>, sharedTransitionScope: SharedTransitionScope, ) { entry<DashboardNavRoute> { val viewModel = hiltViewModel<DashBoardViewModel>() val state by viewModel.uiState.collectAsState() val snackbarHostState = remember { SnackbarHostState() } // 其他组件 DashboardScreen( onEvent = viewModel::onEvent, state = state, snackbarHostState = snackbarHostState, sharedTransitionScope = sharedTransitionScope, animatedContentScope = this, // 此处报错:类型不匹配 ) } }
错误提示
Argument type mismatch: actual type is 'EntryProviderScope', but 'AnimatedContentScope' was expected.
解决方案
在Navigation 3中,entry<DashboardNavRoute>的lambda函数作用域是EntryContentScope,而EntryContentScope继承了AnimatedContentScope,因此只需确保传递的是lambda内部的this(而非外层EntryProviderScope的this)即可。
修改后的DashboardRoot.kt代码:
@OptIn(ExperimentalSharedTransitionApi::class) fun EntryProviderScope<NavKey>.dashboardEntry( backStack : NavBackStack<NavKey>, sharedTransitionScope: SharedTransitionScope, ) { entry<DashboardNavRoute> { val viewModel = hiltViewModel<DashBoardViewModel>() val state by viewModel.uiState.collectAsState() val snackbarHostState = remember { SnackbarHostState() } // 其他组件 DashboardScreen( onEvent = viewModel::onEvent, state = state, snackbarHostState = snackbarHostState, sharedTransitionScope = sharedTransitionScope, animatedContentScope = this, // 此处的this是EntryContentScope,符合AnimatedContentScope类型要求 ) } }
如果担心作用域混淆,也可以显式指定lambda参数:
@OptIn(ExperimentalSharedTransitionApi::class) fun EntryProviderScope<NavKey>.dashboardEntry( backStack : NavBackStack<NavKey>, sharedTransitionScope: SharedTransitionScope, ) { entry<DashboardNavRoute> { entryScope -> val viewModel = hiltViewModel<DashBoardViewModel>() val state by viewModel.uiState.collectAsState() val snackbarHostState = remember { SnackbarHostState() } // 其他组件 DashboardScreen( onEvent = viewModel::onEvent, state = state, snackbarHostState = snackbarHostState, sharedTransitionScope = sharedTransitionScope, animatedContentScope = entryScope, ) } }
说明
Navigation 3的entry函数提供的lambda作用域是专门为导航条目内容设计的EntryContentScope,它内置了导航相关的上下文,同时继承了AnimatedContentScope,完全满足共享元素动画对作用域的需求。之前的错误是因为误将外层EntryProviderScope的this传递给了参数,而正确的作用域是entry函数lambda内部的上下文。
内容的提问来源于stack exchange,提问作者Pierre Vieira

