compose -> NavHost

2,306 阅读6分钟

NavHost

compose里的navhost类似于android以前的navigation库 以前的

<navigation
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/navigation_fitness"
    app:startDestination="@+id/navigation_fitness_types">
    <Fragement .../>
    <Fragment.../>

现在的
a,b,c就是route的名字,到时候可以通过route切换对应的组件

NavHost(navController = controller, startDestination = "a", Modifier.padding(it)) {

composable("a"){
//a 要显示的组件
}
composable("b"){
//b 要显示的组件
}
composable("c"){
}

源码

rememer的特性,可以知道startDestination改变的时候这里也会重组的,所以切换组件也可以动态修改startDestination

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
    )
}

一个一个参数开看:
navController:控制nav组件切换的

NavHostController

public open class NavHostController

(context: Context) : NavController(context) {}

下边列一下常用的方法,看方法名字大概就知道啥意思了

val controller = rememberNavController()
controller.navigate("routename") //跳转到某个组件

controller.graph.findStartDestination() //获取start destination
controller.graph.startDestinationRoute//同上,直接获取start destination的route名字
controller.currentBackStackEntry?.destination?.route //获取当前展示的route的名字
controller.currentDestination

startDestination:初始化加载的组件的名字

builder: NavGraphBuilder.() -> Unit

builder的作用就是添加我们要用到的destination,用到的方法如下,不过一般不用这些,都用扩展的composable方法

public fun <D : NavDestination> destination(navDestination: NavDestinationBuilder<D>) {
    destinations += navDestination.build()
}

/**
 * Adds this destination to the [NavGraphBuilder]
 */
public operator fun NavDestination.unaryPlus() {
    addDestination(this)
}

/**
 * Add the destination to the [NavGraphBuilder]
 */
public fun addDestination(destination: NavDestination) {
    destinations += destination
}

可以用上边的方法直接add,也可以用builder的扩展方法了,如下,其实最终也是调用了addDestination的方法

public fun NavGraphBuilder.composable(
    route: String,
    arguments: List<NamedNavArgument> = emptyList(),
    deepLinks: List<NavDeepLink> = emptyList(),
    content: @Composable (NavBackStackEntry) -> Unit
) {
    addDestination(
        ComposeNavigator.Destination(provider[ComposeNavigator::class], content).apply {
            this.route = route
            arguments.forEach { (argumentName, argument) ->
                addArgument(argumentName, argument)
            }
            deepLinks.forEach { deepLink ->
                addDeepLink(deepLink)
            }
        }
    )
}

简单的伪代码如下
要跳转的话,调用controller的navigate方法,传入对应的route名字就可以切换显示不同的组件了

val controller = rememberNavController()

NavHost(navController = controller, startDestination = "a", Modifier.padding(it)) {

composable("a"){
//route a 要显示的组件
}
composable("b"){
//route b 要显示的组件
}
composable("c"){
//route c 要显示的组件
}

上边也就简单的用了composable的两个参数 route和content,也是必须的两个参数,还有两个参数看下

arguments: List<NamedNavArgument> = emptyList()

composable("route1/{age}/{name}", listOf(navArgument("age") {
    this.defaultValue = 22
}, navArgument("name") {
    defaultValue = "jerry"
})) {
    val age = it.arguments?.get("age")//根据上边定义的key值获取value值
    val name = it.arguments?.get("name")
    Text(text = "route1 compose content, $age ,$name", fontSize = 33.sp)
}

简单说下上边的代码:
composable("route1/{age}/{name}" :斜杠是非必须的,只是为了区分参数,要传递的参数用花括号括起来{age}和{name}
跳转的时候必须传递对应的参数,如下 controller.navigate("route1/33/dock")

navArgument("age") {
    this.defaultValue = 22
}, navArgument("name") {
    defaultValue = "jerry"
}

这里定义了两个参数的key值以及默认值。

navArgument()方法如下,就是返回一个NamedNavArgument对象

public fun navArgument(
    name: String,
    builder: NavArgumentBuilder.() -> Unit
): NamedNavArgument = NamedNavArgument(name, NavArgumentBuilder().apply(builder).build())

另外NavArgument有如下四个属性可以设置

public class NavArgument internal constructor(
    type: NavType<Any?>,//数据类型,比如NavType.StringType
    isNullable: Boolean,//是否可以为null,默认是false的,不参数会异常的
    defaultValue: Any?,//默认值
    defaultValuePresent: Boolean//是否有默认值【包括默认值为空的情况】
) 

上边测试的参数必须有,否则异常
composable("route1/{age}/{name} 对应 controller.navigate("route1/33/dock")

那如果某个参数不是必须的咋办,比如name非必须的,那写法如下:
composable("route1/{age}/?name={name} 对应 controller.navigate("route1/33/")

navArgument("name") {
    defaultValue = "jerry"
    nullable=true //修改默认值可以为null
}

可以为null的参数的写法,问号+名字+等号+花括号+名字,如下?name={name}

composable("route1/{age}/?name={name}"
controller.navigate("route1/11/")//不用传name了

deeplink

有三个参数可以设置,就是Intent以前的跳转方法,可以设置一个action,也可以设置data,或者给个mimeType来查找对应的页面

public class NavDeepLink internal constructor(
    /**
     * The uri pattern from the NavDeepLink.
     *
     * @see NavDeepLinkRequest.uri
     */
    public val uriPattern: String?,
    /**
     * The action from the NavDeepLink.
     *
     * @see NavDeepLinkRequest.action
     */
    public val action: String?,
    /**
     * The mimeType from the NavDeepLink.
     *
     * @see NavDeepLinkRequest.mimeType
     */
    public val mimeType: String?
)

如下,设置给deeplink,这样就多了一种跳转方式了,原本的只能controller.navigate("account2/jerry");现在换一种方式

composable("accounts2/{name}",
    arguments = listOf(
        navArgument("name") {
            type = NavType.StringType
        }
    ),
    deepLinks = listOf(navDeepLink {
        uriPattern = "rally://accounts2/{name}"
        action="my.test.action"

    })){
    Text(text = "temp====${it.arguments?.getString("name")}")
}

①uriPattern

NavDeepLink 里赋值uriPattern = "rally://accounts2/{name}"

            composable("accounts2/{name}",
                deepLinks = listOf(navDeepLink {
                    uriPattern = "rally://accounts2/{name}"
                })){
                Text(text = "temp====${it.arguments?.getString("name")}")
            }

然后这个组件所在activity的清单文件里添加

<Activity android:name="xxx">
<intent-filter >
    <action android:name="android.intent.action.VIEW" />
    <data android:scheme="rally" android:host="accounts2" />
</intent-filter>

跳转操作

val intent=Intent(Intent.ACTION_VIEW)
intent.data="rally://accounts2/${name}2222".toUri()
context.startActivity(intent)

②自定义action

action="my.test.action"

            composable("accounts2/{name}",
                deepLinks = listOf(navDeepLink {
                    action="my.test.action"
                })){

                Text(text = "temp====${it.arguments?.getString("name")}")
            }

清单里修改,记得那个DEFAULT 的category必须有

<Activity android:name="xxx">
<intent-filter>
    <action android:name="my.test.action"/>
    <category android:name="android.intent.category.DEFAULT" />
</intent-filter>

跳转代码

context.startActivity(Intent("my.test.action").apply { putExtra("name","jerry") })

测试结果,没法传递数据,上边的putExtra没用,暂时不研究了

源码解读

上边navHost的简单使用已经完事了,下边简单看下源码,调用naviget(route)如何做到切换不同的compose组件 源码太多了,跳来跳去的都晕了,简单记录下可能用到的代码

NavigatorProvider类里的addNavigator方法

NavController里

init {
    _navigatorProvider.addNavigator(NavGraphNavigator(_navigatorProvider))
    _navigatorProvider.addNavigator(ActivityNavigator(context))
}

rememberNavController()

@Composable
public fun rememberNavController(
    vararg navigators: Navigator<out NavDestination>
): NavHostController {
    val context = LocalContext.current
    return rememberSaveable(inputs = navigators, saver = NavControllerSaver(context)) {
        createNavController(context)
    }.apply {
        for (navigator in navigators) {
            navigatorProvider.addNavigator(navigator)
        }
    }
}

private fun createNavController(context: Context) =
    NavHostController(context).apply {
        navigatorProvider.addNavigator(ComposeNavigator())
        navigatorProvider.addNavigator(DialogNavigator())
    }

看下这几个类的注解

@Navigator.Name("navigation")
public open class NavGraphNavigator
@Navigator.Name("activity")
public open class ActivityNavigator
@Navigator.Name("composable")
public class ComposeNavigator : Navigator<Destination>()
@Navigator.Name("dialog")
public class DialogNavigator : Navigator<Destination>()

看下addNavgator方法,点进去看下逻辑,就是拿到class类的注解名字(就是上边Name括号里的名字),把这个名字当做key,存到 private val _navigators: MutableMap<String, Navigator> = mutableMapOf()

public fun addNavigator(
    navigator: Navigator<out NavDestination>
): Navigator<out NavDestination>? {
    return addNavigator(getNameForNavigator(navigator.javaClass), navigator)
}

后边会通过get获取map里的数据。

public inline operator fun <T : Navigator<out NavDestination>> NavigatorProvider.get(
    clazz: KClass<T>
): T = getNavigator(clazz.java)

NavHost

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
    )
}
public fun NavHost(
    navController: NavHostController,
    graph: NavGraph,
    modifier: Modifier = Modifier
) {
    //...
        // Then set the graph
    navController.graph = graph
     val saveableStateHolder = rememberSaveableStateHolder()

    // Find the ComposeNavigator, returning early if it isn't found
    // (such as is the case when using TestNavHostController)
    val composeNavigator = navController.navigatorProvider.get<Navigator<out NavDestination>>(
        ComposeNavigator.NAME //"composable"
    ) as? ComposeNavigator ?: return//这个就是前边addNavigator里的东西,通过get获取
    val backStack by composeNavigator.backStack.collectAsState()
    val transitionsInProgress by composeNavigator.transitionsInProgress.collectAsState()
    val visibleTransitionsInProgress = rememberVisibleList(transitionsInProgress)
    val visibleBackStack = rememberVisibleList(backStack)
    visibleTransitionsInProgress.PopulateVisibleList(transitionsInProgress)
    visibleBackStack.PopulateVisibleList(backStack)

    val backStackEntry = visibleTransitionsInProgress.lastOrNull() ?: visibleBackStack.lastOrNull()

    var initialCrossfade by remember { mutableStateOf(true) }
    if (backStackEntry != null) {
        // while in the scope of the composable, we provide the navBackStackEntry as the
        // ViewModelStoreOwner and LifecycleOwner
        Crossfade(backStackEntry.id, modifier) {
            val lastEntry = transitionsInProgress.lastOrNull { entry ->
                it == entry.id
            } ?: backStack.lastOrNull { entry ->
                it == entry.id
            }

    //下边这个就是要显示的compose了
            lastEntry?.LocalOwnersProvider(saveableStateHolder) {
                (lastEntry.destination as ComposeNavigator.Destination).content(lastEntry)
    //这个content(lastEntry)就是composable("route"){ }里的组件了
            }
    

navController.graph = graph 补充代码:

onGraphCreated(startDestinationArgs)
   

go on

_navigatorProvider.navigators.values.filterNot { it.isAttached }.forEach { navigator ->
    val navigatorBackStack = navigatorState.getOrPut(navigator) {
        NavControllerNavigatorState(navigator)
    }
    navigator.onAttach(navigatorBackStack)
}

composable

public fun NavGraphBuilder.composable(
    route: String,
    arguments: List<NamedNavArgument> = emptyList(),
    deepLinks: List<NavDeepLink> = emptyList(),
    content: @Composable (NavBackStackEntry) -> Unit
) {
    addDestination(
        ComposeNavigator.Destination(provider[ComposeNavigator::class], content).apply {
            this.route = route
            arguments.forEach { (argumentName, argument) ->
                addArgument(argumentName, argument)
            }
            deepLinks.forEach { deepLink ->
                addDeepLink(deepLink)
            }
        }
    )
}

看下Destination

@NavDestination.ClassType(Composable::class)
public class Destination(
    navigator: ComposeNavigator,
    internal val content: @Composable (NavBackStackEntry) -> Unit
) : NavDestination(navigator)

之前看到的下边的代码用到的就是上边这个Destinaion

(lastEntry.destination as ComposeNavigator.Destination).content(lastEntry)    

切换composable

第一种方法:动态修改startDestination可以做到 第二种方法:navController.navigate("route name") 逻辑,前边有贴过

val backStack by composeNavigator.backStack.collectAsState()
val transitionsInProgress by composeNavigator.transitionsInProgress.collectAsState()
val visibleTransitionsInProgress = rememberVisibleList(transitionsInProgress)
val visibleBackStack = rememberVisibleList(backStack)
visibleTransitionsInProgress.PopulateVisibleList(transitionsInProgress)
visibleBackStack.PopulateVisibleList(backStack)

navigate方法执行的时候会修改backStack的值,会导致相关的组件重组 navigate方法一路往下点

navigate(node, args, navOptions, navigatorExtras)
navigator.navigateInternal(listOf(backStackEntry), navOptions, navigatorExtras) {
    navigated = true
    addEntryToBackStack(node, finalArgs, it)
}

可以看到修改了state

public open fun navigate(
    entries: List<NavBackStackEntry>,
    navOptions: NavOptions?,
    navigatorExtras: Extras?
) {
    entries.asSequence().map { backStackEntry ->
        val destination = backStackEntry.destination as? D ?: return@map null
        val navigatedToDestination = navigate(
            destination, backStackEntry.arguments, navOptions, navigatorExtras
        )
        when (navigatedToDestination) {
            null -> null
            destination -> backStackEntry
            else -> {
                state.createBackStackEntry(
                    navigatedToDestination,
                    navigatedToDestination.addInDefaultArgs(backStackEntry.arguments)
                )
            }
        }
    }.filterNotNull().forEach { backStackEntry ->
        state.push(backStackEntry)
    }
}

这个state前边有贴代码,是在attach的时候赋值的

public open fun onAttach(state: NavigatorState) {
    _state = state
    isAttached = true
}

源码跳来跳去类太多了,看的头晕,不看了,简单会用就行了