5 .2 Navigation-Compose 框架简单解析

1,065 阅读4分钟

NavDestination:NavGraph 中的节点,每一个 NavDestination 都代表一个可以被导航到的目的地。

Compose 中由 Destination 来实现,除了 route、arguments、deepLinks 还新增了 @Composable content 属性,用来指定 Destination 对应的 Compose UI。

NavGraph:保存所有 NavDestination 的集合,作为参数传给 NavHost 方法。在 NavHost 方法中保存到 NavController 的 graph 属性中。本身继承自 NavDestination ,使得可以创建出树形结构的嵌套 NavGraph (或者说可以分组创建 NavGraph).

NavController:控制 NavHost 中的导航,在导航时维护 back 栈。只能在 graph 属性中可以已有的 Destination 之间导航。可以通过 graph 属性的 addDestination 方法动态添加 Destination 。

NavHost:导航的容器。 本身在 Compose 树中,用于显示栈顶 Destination 的 content。

30CBD707-51B7-4B7B-84A9-6BFBF6EFF6D1.png

  • 每一个 NavHost 都有一个 NavController , 每一个 NavController 都有一个 NavGraph 。
  • NavHost 存在于 compose 树中用于显示 NavController back 栈顶对象的 @Composable content 。
  • 导航时 NavController 检索 NavGraph 从中找到对应的 Destination 并将其添加到 back 栈顶后触发重组更新 NavHost 显示的 @Composable content

NavHost

部分源码( /....../ 代表省略源码),简单了解 NavHost 流程

@Composable
public fun NavHost(navController: NavHostController,graph: NavGraph,
    modifier: Modifier = Modifier) {
    /*......*/
    //将 navController 和 graph 关联起来 
    navController.graph = graph
    /*......*/。
    //监听 backStack 和 transitionsInProgress 状态变化
    val backStack by composeNavigator.backStack.collectAsState()
    val transitionsInProgress by composeNavigator.transitionsInProgress.collectAsState()
    /*......*/
    //找到即将成为栈顶 或 栈顶的 backStackEntry 
    val backStackEntry = visibleTransitionsInProgress.lastOrNull() ?: visibleBackStack.lastOrNull()
    /*......*/
    if (backStackEntry != null) {
      	/*......*/
      	//淡入淡出
        Crossfade(backStackEntry.id, modifier) {
          /*......*/
          	//显示 content (详情 NavDestination 小节)
			(lastEntry.destination as ComposeNavigator.Destination)
          	.content(lastEntry)
          /*......*/
        }
    }
  /*......*/
}

我们在 创建 NavHost 中使用的并不是上面的方法,而是下面的方法。

@Composable
public fun NavHost(
    navController: NavHostController,
    startDestination: String,
    modifier: Modifier = Modifier,
    route: String? = null,
    builder: NavGraphBuilder.() -> Unit
) {
    NavHost(
 		navController,
      	//!!!
        remember(route, startDestination, builder) {
            navController.createGraph(startDestination, route, builder)
        },
        modifier
    )
}

参数上对 NavGraph 的生成做了拆解,还添加了 builder 方便我们在 lambda 中使用 NavGraphBuilder.composable() 来配置 Destination。

NavHost(navController = navController, startDestination = Screen.First.route){
    composable("FirstScreen"){
        FirstScreen(navController)
    }
    composable("SecondScreen"){
        SecondScreen(navController)
    }
}

重点!!! 对于 NavGraph 传参使用了 remember 方法 。

这意味调用这个 NavHost 方法必须一次性将所有 Destination 全部在 builder 中配置好,因为无论 route, startDestination, builder 哪个 key 改变都会触发 remember 的 calculation 生成新的 navGraph ,从而导致 NavHost 重组,新的 navGraph 传入 ,再次执行 navController.graph = graph 。 

navController.graph 的重新赋值会导致 navController清空现有的 back 栈,导航到新 navGraph 的 startDestination。

AB6FE63A-5C64-4368-AE6A-D6A7F7E1B8EB.png

但是 可以通过 navController.graph 的 addDestination 方法动态添加 Destination 。

rememberNavController

16744BED-6FAC-450F-B344-122CD4D30F77.png

  1. 创建 NavHostController 实例
  2. 给 NavHostController 的 navigatorProvider 添加 ComposeNavigator 和 DialogNavigator
  3. 如果参数中传入了 Navigator 也将其添加到 navigatorProvider
  4. 用 rememberSaveable 的方式保存创建好的 NavHostController 实例并返回

NavHostController 是 NavController 的子类,NavController init 初始化的时候会默认添加 NavGraphNavigator 和 ActivityNavigator。

FE3112F0-B18E-4E26-B8DD-E73C78558810.png NavHostController 实例 navigatorProvider 属性包含 NavGraphNavigator 、 ActivityNavigator、ComposeNavigator 和 DialogNavigator 。

Navigator

NavController 导航到 Destination  实际是由 Navigator 完成的。

简单看下 Activity 和 Compose  的 Destination 源码可以看出,不同类型的 Destination 封装的信息的侧重点不同。

Activity 的 Destination 侧重于能匹配到对应 Activity 的信息, Compose 的 Destination 中则包含了对应 UI 的 @composeable content。

3BC9F6A7-5386-4968-9AA9-260D57BCC277.png

框架中 Navigator 是个抽象类,子类有 ActivityNavigator、ComposeNavigator、DialogNavigator、NavGraphNavigator、NoOpNavigator。

除了 NoOpNavigator 只有创建 NavDestination 功能外,其他子类分别负责不同类型 Destination 的导航,并且通过注解指定了 Navigator.Name。

@Navigator.Name("composable")
public class ComposeNavigator : Navigator<Destination>() {
}

NavController  navigatorProvider 属性中 Navigator 的种类决定了 NavController 可以处理哪些类型 Destination 的导航

NavController.navigate()

02868D46-8B53-45E6-987A-56D0D89BA1A5.png

NavGraph

NavGraph 是保存 NavDestination 的集合提供了增、删、查、迭代 api,

重要属性:

  • nodes: SparseArrayCompat
    • 保存 NavGraph 中所有子节点
  • startDestId: Int  / startDestIdName: String?
    • 用于指定 NavGraph 默认启动的 NavDestination

介绍 NavDestination 时,重点标记过 NavGraph 也是一种 NavDestination,而且 NavController init 时也会给 navigatorProvider 添加 NavGraphNavigator 。 

如果要导航到的 NavDestination 是 NavGraph 类型, NavGraphNavigator 会将导航到 startDestId/startDestIdName 属性对应的 NavDestination。(详细介绍放在 navigation 流程 )

这样也使得 NavGraph 可以分组配置,类似 ViewGroup - View 的嵌套结构。

8D9E938B-A0AB-41AA-B0F1-920E78334189.png

  1. 使用 navController 中的 navigatorProvider 和 传入的参数 创建 NavGraphBuilder 对象
  2. apply(builder)  执行传入的 builder , builder 中的 composable 被执行 将 DSL 转换成 ComposeNavigator.Destination 添加到 NavGraphBuilder 的 destinations 属性中
  3. build()  创建 NavGraph 对象 ,将 NavGraphBuilder 中的属性设置给  NavGraph

使用时我们并没有直接创建 NavGraph ,而是将 NavGraphBuilder 作为参数传递给 HavHost() 方法。

0AD8669B-B8E4-4506-8F56-3A28BA788D21.png

NavGraph 创建是在 NavHost() 方法中完成的

41A9D276-D577-4B9A-A10D-D7732EE940A7.png

NavGraphBuilder

创建 NavGraph 的 DSL, 继承 NavDestinationBuilder 。 重要属性:

  • provider: NavigatorProvider
    • 用于创建 NavDestination
  • startDestinationId: Int / startDestinationRoute: String  
    • 对应 NavGraph 的 startDestId/startDestIdName
  • destinations: MutableList
    • 对应 NavGraph 的 nodes

NavGraphBuilder.build

查看 NavDestinationBuilder 源码会发现  NavDestinationBuilder 是用来创建单个 NavDestination 的,而 NavGraphBuilder 是用来创建整个 NavGraph(1 个 NavGraph 对象 和 0~n 个 NavDestination 节点) 的。

build 方法是如何完成构建逻辑的呢?

用 super.builder() 创建一个 NavGraph 对象(NavGraphBuilder 声明的时父类泛型是 NavGraph ),再将自己的属性设置给 NavGraph 对象。

95AFF2C6-C8B9-4090-A95A-F892ED9558CA.png

NavGraphBuilder.composable

使用 NavGraphBuilder 时我们通常配合 composable DSL 一起。

0AD8669B-B8E4-4506-8F56-3A28BA788D21.png

composable DSL 执行时会将所有参数封装成 ComposeNavigator.Destination 并调用 NavGraphBuilder.addDestination() 方法将其添加到 destinations 属性中。

CF770671-FA44-4DB7-802A-A41BC0D305C2.png

简单解析只涉及了使用时必须知道的类和流程,完整的 Navigation-Compose 组件要复杂的多。