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
}
源码跳来跳去类太多了,看的头晕,不看了,简单会用就行了