Compose导航学习(二)

261 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第24天,点击查看活动详情

概述

在上一篇笔记中学习在Compose中使用导航组件进行一些基本的导航操作,本篇学习笔记在之前的基础上,将会学习如何使用和创建深层链接,通过深层链接,可以允许外部应用或者网页直接导航到深层链接指定的目的地。同时会学习如何使用嵌套导航图,嵌套导航图对于分模块开发的项目来说非常有用,同时反过来,嵌套导航图也能够帮助我们对项目模块进行清晰地划分。最后会学习在底部导航栏中使用导航组件,通过这部分的学习,能够帮助我们处理底部导航栏在切换的时候的导航处理。

深层链接

和之前使用导航组件一样,Compose也支持深层链接,使用深层链接,能够方便我们从网页或者其它应用跳转到指定的目的地。composable函数允许我们向其中传递深层链接的相关数据。

下面的代码中我们向一个可组合项设置了深层链接的属性,如下所示:

        //登录页面深层链接的匹配信息
        const val ROUTE_LOGIN_DEEP_LINK = "abc://www.example.com"
        composable("${RouteConfig.ROUTE_LOGIN}/login",deepLinks = listOf(
            navDeepLink {
                this.uriPattern = "${RouteConfig.ROUTE_LOGIN_DEEP_LINK}/login"
            }
        )) {
            LoginCompose(controller,null)
        }

在登录页面中我们定义了深层链接,其中的匹配规则是"abc://www.example.com/login"。

这里只是在登录可组合项上定义了深层链接,要想使用深层链接,我们还必须将这个规则配置到对应的Activity中,如下所示:

    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <data
            android:scheme="abc"
            android:host="www.example.com"
            />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>

这里我们定义了深层链接的schemehost,当能够匹配到这两个参数的时候就可以跳转到这个Activity上,之后再根据路径上的/login去匹配是否跳转到登录页面。

在其它应用的跳转逻辑如下:

    startActivity(Intent().apply {
        this.data = Uri.parse("abc://www.example.com/login")
    })

运行上面的代码,就可以直接从别的应用跳转到当前应用的登录页面。

除了可以从别的应用跳转到当前应用,我们可以通过深层链接配置PendingIntent,这样就可以在应用微件或者通知中跳转到对应的页面,下面的代码演示了创建一个PendingIntent的方法:

    val intent = Intent().apply {
        this.data = Uri.parse("abc://www.example.com/1000")
    }
    val pendingIntent = TaskStackBuilder.create(context).run {
        addNextIntentWithParentStack(intent)
        getPendingIntent(0,PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
    }

将这个PendingIntent设置给通知,就可以从通知中打开登录页面了。

嵌套导航图

在之前学习导航组件的时候,我们可以使用嵌套图,使用嵌套图可以帮助我们缓解导航图不断变大的窘境,让我们的导航图代码更加容易阅读,同时还可以帮助我们对项目模块进行拆分。

Compose中同样支持我们使用嵌套图,我们可以使用navigation扩展函数来设置嵌套图,如下代码所示:

    NavHost(navController = controller, startDestination = RouteConfig.ROUTE_HOME) {
        
        ...通过composable直接定义的可组合项目的地...
        
        //嵌套导航图
        navigation(
            startDestination = RouteConfig.ROUTE_ORDER_SHOW,
            route = RouteConfig.ROUTE_ABOUT_ORDER
        ) {
            composable(RouteConfig.ROUTE_ORDER_SHOW) {
                OrderShowCompose(controller = controller)
            }
            composable(RouteConfig.ROUTE_ORDER_PAY) {
                OrderPayCompose(controller = controller)
            }
            composable(RouteConfig.ROUTE_ORDER_PAY_RESULT) {
                OrderPayResultCompose(controller = controller)
            }
        }
    }

上面的代码中我们就使用了嵌套导航图,我们在导航到嵌套图内部的可组合项目的地的时候,可以使用navigation中的route参数定义的值,也可以使用composable中的route定义的值。

在导航图变大的时候,我们还可以将导航图拆分成多个方法,这对于分模块开发的项目是很有用的,拆分方法的时候,我们需要定义成NavGraphBuilder的扩展方法,如下面的代码所示:

//将订单部分的导航图拆分到另一个方法中
fun NavGraphBuilder.orderNav(controller: NavController){
    navigation(
        startDestination = RouteConfig.ROUTE_ORDER_SHOW,
        route = RouteConfig.ROUTE_ABOUT_ORDER
    ) {
        composable(RouteConfig.ROUTE_ORDER_SHOW) {
            OrderShowCompose(controller = controller)
        }
        composable(RouteConfig.ROUTE_ORDER_PAY) {
            OrderPayCompose(controller = controller)
        }
        composable(RouteConfig.ROUTE_ORDER_PAY_RESULT) {
            OrderPayResultCompose(controller = controller)
        }
    }
}

定义这个方法之后,我们就可以将这个方法放在NavHost中进行调用:

NavHost(navController = controller, startDestination = RouteConfig.ROUTE_HOME) {
    ......
    orderNav(controller = controller)
    ......
}

和底部导航栏集成

很多应用都可能会使用到底部导航栏,通常我们可能会使用BottomNavigation来创建底部导航栏,底部导航栏的每个item都对应一个可组合项,之前我们可能会使用Fragment来做这个操作,但是在Compose中,我们可以将NavHost直接传递给父可组合项,父可组合项可以直接使用其中定义的可组合项。

同时为了响应点击导航栏的按钮切换页面的显示,我们可以使用NavController.currentBackStackEntryAsState()来获取当前的返回堆栈,从中可以获取到NavDestination.然后通过hierarchy()将该项的路由与当前目的地及其父目的地的路由进行比较来确定每个BottomNavigationItem的选定状态。

另外,我们需要通过BottomNavigationItem提供的onClick回调来处理用户点击某一项之后的目的地导航,仍然通过navigate()去执行导航,同时在执行导航的时候,我们可以使用saveStaterestoreState标志,在底部导航的不同项之间切换的时候,系统会正确保存并恢复该项的状态和返回堆栈。

下面的代码演示了使用底部导航栏进行导航的操作:

    //只适用于当前页面的controller
    val currentNavController = rememberNavController()
    //记录当前选中的目的地
    val navBackStackEntry by currentNavController.currentBackStackEntryAsState()
    val currentDestination = navBackStackEntry?.destination

    Scaffold(modifier = Modifier.fillMaxWidth(), topBar = {
        NormalTitle(controller = controller, title = "底部导航栏")
    }, bottomBar = {
        BottomNavigation(modifier = Modifier.fillMaxWidth()) {
            bottoms.forEach {
                BottomNavigationItem(selected = currentDestination?.hierarchy?.any { cur ->
                    cur.route == it.route
                } == true, onClick = {
                    currentNavController.navigate(it.route) {
                        //每次点击一个新的项目,都会将起始目的地和当前目的地之间的目的地销毁,否则每次点击都会创建一个新的目的地堆栈添加到返回堆栈中
                        //按系统的返回键的时候会一直回退之前添加的可组合项
                        popUpTo(currentNavController.graph.findStartDestination().id) {
                            //保存状态,如果不设置此项和下面的restoreState = true的话,那么如果目的地中包含状态,
                            //目的地导航离开后状态将无法恢复,目的地中的状态需要使用rememberSaveable保存才可以恢复
                            saveState = true
                        }
                        //设置此项将会使当前目的地和起始目的地之间只会有一个当前目的地实例,因为上面popUpTo每次都会回退到起始目的地上
                        //如果不设置此项,返回栈中将会存在多个起始目的地的实例
                        launchSingleTop = true
                        //恢复状态
                        restoreState = true
                    }
                }, icon = {
                    Icon(imageVector = it.icon, contentDescription = it.content)
                }, label = {
                    Text(text = it.content)
                }, unselectedContentColor = Color.White, selectedContentColor = Color.Magenta)
            }
        }
    }) {
        NavHost(navController = currentNavController, startDestination = "message") {
            composable("message") {
                MessageCompose(controller = controller)
            }

            composable("contacts") {
                ContactsCompose(controller = controller)
            }

            composable("favorites") {
                FavoriteCompose(controller = controller)
            }
        }
    }

运行上面的代码会得到如下的效果:

BottomNavigation中使用导航