Jetpack Compose Navigation:直接导航到非 startDestination 的嵌套图中的路由

xsh*_*ake 13 navigation android kotlin android-jetpack-navigation android-jetpack-compose

我正在开发 Jetpack Compose Navigation 演示,并且有一个嵌套导航图,其中包含两个不同的嵌套路线以及每个嵌套路线的屏幕:

  • 登录图
  • 主图

登录图具有三种路线,用于显示三个不同的屏幕

  • 路由“login”用于显示登录屏幕
  • 路由“register”用于显示RegisterScreen
  • 路由“recoverPassword”用于显示 RecoverPasswordScreen

主图有两个用于这些屏幕的路线

  • 路由“home”以显示主屏幕
  • 路由“设置”以显示设置屏幕

嵌套图创建在MainActivity.kt中调用

setContent {
        NavigationDemoTheme {

            val navController = rememberNavController()
            SetupNavGraph(navController = navController)
        }
    }
Run Code Online (Sandbox Code Playgroud)

文件NestedNavGraph.kt中的函数如下所示:

fun SetupNavGraph(navController: NavHostController) {
    NavHost(navController = navController, startDestination = "login_route")
    {
        loginGraph(navController = navController)
        mainGraph(navController = navController)
    }
}
Run Code Online (Sandbox Code Playgroud)

在文件LoginNavGraph.kt中,我定义了路线和起始目的地

fun NavGraphBuilder.loginGraph(navController: NavController) {
    navigation(startDestination = "login", route = "login_route") {
        composable(route = "login") {
            LoginScreen(navController = navController)
        }

        composable(route = "register") {
            RegisterScreen(navController = navController)
        }

        composable(route = "recover") {
            RecoverPasswordScreen(navController = navController)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在文件MainNavGraph.kt中,我定义了这两条路线和这个起始目的地:

 navigation(startDestination = "home", route = "main_route") {

        composable(route = "home") { 
            HomeScreen(navController = navController)
        }

        composable(route = "settings") { 
            SettingsScreen(navController = navController)
        }
    }
Run Code Online (Sandbox Code Playgroud)

我现在的问题是:如何从设置屏幕显示恢复密码屏幕。我知道我可以从 SettingsScreen 导航到“login_route”,但随后将显示 startDestination,即 LoginScreen。

// shows the LoginScreen because the startDestination in the "login_route" is set to "login"
navController.navigate(route = "login_route")
   
Run Code Online (Sandbox Code Playgroud)

那么,如何直接导航到嵌套图路由“login_route”中的路由“recover”?我想到了以下“解决方法”:

将参数传递给“login_route”,例如:

navController.navigate(route = "login_route?destination=recover")
Run Code Online (Sandbox Code Playgroud)

然后,我将只有一条路线作为目的地,例如“LoginView”。这将改变登录图,如下所示:

fun NavGraphBuilder.loginGraph(navController: NavController) {

    navigation(startDestination = "login_view, route = "login_route/{destination}) {

        composable(
            route = "login_view",
            arguments = listOf(
                navArgument("destination") { defaultValue = "login" },
            )
        ) { backStackEntry ->

            val destination =  backStackEntry.arguments?.getString("destination");

            destination?.let { destination ->  
                LoginView(destination = destination)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

LoginView 是可组合的,它将有一个自己的 NavHost,我可以在其中使用上一个路由中的查询参数设置 startDestination:

fun LoginView( destination : String = "login"){

    val navController = rememberNavController()
    var startDestination = destination;

    Scaffold ()
    {

        NavHost(
            navController = navController,
            startDestination = startDestination
        ) {

           composable(route = "login") {
             LoginScreen(navController = navController)
           }

           composable(route = "register") {
             RegisterScreen(navController = navController)
           }

           composable(route = "recover") {
             RecoverPasswordScreen(navController = navController) 
           }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我应该能够通过以下命令从 SettingsScreen 调用 RecoverPasswordScreen:

navController.navigate(route = "login_route?destination=recover")
Run Code Online (Sandbox Code Playgroud)

另一种可能性是在定义的 MainGraph 中为 RecoverPassword Screen 提供额外的路由。是否有其他可能性可以直接访问嵌套图中的路线?如果可以在路由到“login_route”时动态更改 startDestination 那就太好了,但我不知道如何或是否可能这样做。

Der*_*k K 5

一种可能的解决方案是使用导航图中定义的深层链接 - 它们也适用于嵌套目的地。然后,您可以使用navController.navigate(deepLinkUri)


Fer*_*nd -1

Compose 允许您(使用参数导航)。这允许您导航到所谓的“嵌套路由”,即屏幕中的特定部分。

现在,这是一个简单的解释,我可以离开你并让你弄清楚。但我认为这对您没有帮助,因为我认为您已经以困难的方式实现了导航。因此,为什么尝试导航会更复杂一些。

这是一种更好的实现方法,以便您想要的导航(从设置屏幕恢复密码屏幕)更容易。

免责声明

将Main中的任何内容更改为您的 AppName。

我还没有添加你所有的屏幕

主屏类

//you could pass in parameters if needed into this constructor
enum class MainScreen(){
//these are your screens
   LogIn(),
   Settings(),
   Recover(),
   Home();

 companion object {
        fun fromRoute(route: String?): MainScreen =
            when (route?.substringBefore("/")) {
                LogIn.name -> LogIn
                Home.name -> Home
                Settings.name -> Settings
                Recover.name -> Recover
                //add the remaining screens
                // a null route resolves to LogInScreen.
                null -> LogIn
                else -> throw IllegalArgumentException("Route $route is not recognized.")
            }
    }

}
Run Code Online (Sandbox Code Playgroud)

主要活动类别

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MainApp()
        }
    }
}

@Composable
fun MainApp() {
    MainTheme {
        val allScreens = MainScreen.values().toList()
        val navController = rememberNavController()
        val backStackEntry = navController.currentBackStackEntryAsState()
        // currentScrren user is on good if app is large
        val currentScreen = MainScreen.fromRoute(
            backStackEntry.value?.destination?.route
        )
        //Using scaffold is a good idea
        Scaffold(
           //add topAppBar and all other things here
        ) { innerPadding ->
            MainNavHost(navController = navController, modifier = Modifier.padding(innerPadding))

        }
    }
}

//Scaffold requires innerPadding so remove if you decide not to use scaffold
@Composable
fun MainNavHost(navController: NavHostController, modifier: Modifier = Modifier) {
    NavHost(
        navController = navController,
        startDestination = LogIn.name,
        modifier = modifier
    ) {
        composable(LogIn.name) {
            /**
             Your body for logIn page
            **/

        }
//this is how you will navigate to Recover Screen from settings
        composable(Settings.name) {
            SettingsBody(onClickRecoverScreen = {navController.navigate(Recover.name)})

            }
        }
          composable(Recover.name) {
             /**
             Your body for Recover page
            **/
        }
        composable(Home.name) {
             /**
             Your body for Home page
            **/
        }
        


}


Run Code Online (Sandbox Code Playgroud)

设置屏幕

@Composable
fun SettingsBody(
    //this callback is how you will navigate from Settings to RecoverPassword
    onClickRecoverScreen: () -> Unit = {},
) {
    Column(
       //Add your designs for this screen
    ) {
        Button(onClick = {onClickRecoverScreen})
    }
}
Run Code Online (Sandbox Code Playgroud)

这是实现导航的最简单的方法(在我看来),因为您可以简单地添加回调来导航到应用程序中的不同位置,并且它更具可测试性(如果您测试;))和可扩展性。您还可以添加深层链接并使用参数(如上所述)导航到应用程序的特定部分(例如,帐户屏幕中的特定帐户)

如果您想了解更多信息,我强烈推荐这个导航代码实验室。