Navigation 是 Android 导航组件,用于页面间跳转
Compose Navigation 依赖
// build.gradle
dependencies {
implementation "androidx.navigation:navigation-compose:2.5.3"
// ...
}
NavController
简介
- 可跟踪返回堆栈可组合条目、使堆栈向前移动、支持对返回堆栈执行操作,以及在不同目的地状态之间导航
- 是导航的核心,设置
Compose Navigation时必须先创建它 - 负责在目标页面(即应用中的屏幕)之间导航
创建
调用 rememberNavController() 函数获取 NavHostController
NavHostController是NavController类的子类,可提供与NavHost可组合项搭配使用的额外功能
@Composable
fun Test() {
val navHostController = rememberNavController()
// ...
}
- 将
NavController放置在可组合项层次结构的顶层,即不向下传递 - 确保
NavController是在可组合屏幕之间导航和维护返回堆栈的主要可信来源
Navigation 的 3 个主要部分是 NavController、NavGraph 和 NavHost
每个
NavController都必须与一个NavHost相关联。
NavHost
NavHost 充当容器,负责显示 NavGraph 的当前目的地
在可组合项之间进行导航时,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
)
}
navController:NavHostController类的实例。您可以使用此对象在屏幕之间导航,例如,通过调用navigate()方法导航到另一个目标页面。您可以通过从可组合函数调用rememberNavController()来获取NavHostController。startDestination:此字符串路线用于定义应用首次显示NavHost时默认显示的目标页面
NavHost 需要指定 startDestination: 启动时显示的目的地
builder: NavGraphBuilder.() -> Unit 负责定义和构建导航图
在 NavHost 的内容函数中,调用 composable() 函数。composable() 函数有两个必需参数。
-
route: 与路线名称对应的字符串。这可以是任何唯一的字符串 -
content:您可以在此处调用要为特定路线显示的可组合项 -
arguments:
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)
}
}
)
}
composable()函数是NavGraphBuilder的扩展函数
NavGraph 导航图
用于映射要导航到的可组合项目标页面
builder 形参要求使用 NavGraphBuilder.composable 扩展函数,将各个可组合目的地添加到导航图中,并定义必要的导航信息。
@Composable
fun RallyNavHost() {
NavHost(
navController = navController,
startDestination = Overview.route,
modifier = modifier
) {
composable(route = Overview.route) {
}
}
}
// NavGraphBuilder.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)
}
}
)
}
导航
通过 navController.navigate(route) 执行导航操作
为了使代码具有可测试性且可重复使用,建议不要将整个
navController直接传递给可组合项。不过,您应该始终提供回调,定义您希望触发的确切导航操作。
navigateUp
popBackStack
- 跳转移除返回堆栈中的所有屏幕,并返回起始屏幕
navController.popBackStack(@IdRes destinationId: Int, inclusive: Boolean)
- destinationId : 表示您希望返回到的目标页面的路线
- inclusive: 如果为 true,会移除指定路线;为 false,
popBackStack()将移除起始目标页面之上的所有目标页面,但不包含该起始目标页面,并仅留下该起始目标页面作为最顶层的屏幕显示给用户
launchSingleTop
- 确保返回堆栈顶部最多只有给定目的地的一个副本,Compose Navigation API 提供了一个
launchSingleTop标志
navController.navigate(route) { launchSingleTop = true }
popUpTo
popUpTo(startDestination) { saveState = true }- 弹出到导航图的起始目的地,以免在您选择标签页时在返回堆栈上构建大型目的地堆栈
restoreState
restoreState = true- 确定此导航操作是否应恢复
PopUpToBuilder.saveState或popUpToSaveState属性之前保存的任何状态。请注意,如果之前未使用要导航到的目的地 ID 保存任何状态,此项不会产生任何影响
fun NavHostController.navigateSingleTopTo(route: String) {
this.navigate(route) {
popUpTo(this@navigateSingleTopTo.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
currentBackStackEntryAsState
如需以 State 的形式获取返回堆栈中当前目的地的实时更新,您可以使用 navController.currentBackStackEntryAsState(),然后获取其当前 destination
currentBackStack?.destination 会返回 NavDestination
参数传递
具名实参的定义方式是附加到路线并用花括号括起来,如下所示:{argument}
Tips:为了提高代码安全性和处理任何极端情况,可以将默认值设置为实参并明确指定其类型。
步骤:
- 如需在导航时随路线一起传递实参,需要按照以下模式将它们附加在一起:
"route/{argument}" - 参数列表,
NavGraphBuilder.composable声明接受参数类型,定义其arguments形参;可以根据需要定义任意数量的实参,因为composable函数默认接受实参列表;(如未明确设置类型,系统将根据此实参的默认值推断出其类型) - 获取参数,通过
navBackStackEntry.arguments?.getString(KEY)获取
每个
NavHost可组合函数都可以访问当前的NavBackStackEntry,该类用于保存当前路线的相关信息,以及返回堆栈中条目的已传递实参
默认情况下,所有参数都会被解析为字符串。composable() 的 arguments 参数接受 NamedNavArgument 列表。您可以使用 navArgument 方法快速创建 NamedNavArgument,然后指定其确切 type 类型:
NavHost(startDestination = "profile/{userId}") {
...
composable(
"profile/{userId}",
arguments = listOf(navArgument("userId") { type = NavType.StringType })
) {...}
}
可选参数
特点:
- 可选参数必须使用查询参数语法 (
"?argName={argName}") 来添加 - 可选参数必须具有
defaultValue集或nullability = true(将默认值隐式设置为null)
composable(
route = "profile?userId={userId}",
arguments = listOf(navArgument("userId") { defaultValue = "user1234" })
) { backStackEntry ->
Profile(navController, backStackEntry.arguments?.getString("userId"))
}
deepLink 深层链接
调用深层链接,Android 可以打开到相应页面
使用 navDeepLink 方法创建 NavDeepLink
由于向外部应用公开深层链接这一功能默认处于未启用状态,因此您还必须向应用的 manifest.xml 文件添加 <intent-filter> 元素
步骤:
AndroidManifest.xml添加深层链接,通过<activity>内的<intent-filter>创建新的过滤器,<action>类型为 VIEW,类别为BROWSABLE和DEFAULT- 使用
<data>添加sheme和host,如下 - 代码中响应 intent,给
NavGraphBuilder.composable中参数deepLinks赋值; - adb 测试 deepLink,命令如下
adb shell am start -d "rally://single_account/Checking" -a android.intent.action.VIEW
// Manifest.xml
<activity
android:name=".RallyActivity"
android:windowSoftInputMode="adjustResize"
android:label="@string/app_name"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
// deepLink
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="rally" android:host="single_account" />
</intent-filter>
</activity>
@Composable
fun Test() {
...
composable(
route = SingleAccount.routeWithArgs,
// ...
deepLinks = listOf(navDeepLink {
uriPattern = "rally://${SingleAccount.route}/{${SingleAccount.accountTypeArg}}"
})
) { ... }
}
Nested Navigation 嵌套导航
需向 NavHost 添加嵌套图,您可以使用 navigation 扩展函数:
NavHost(navController, startDestination = "home") {
...
// Navigating to the graph via its route ('login') automatically
// navigates to the graph's start destination - 'username'
// therefore encapsulating the graph's internal routing logic
navigation(startDestination = "username", route = "login") {
composable("username") { ... }
composable("password") { ... }
composable("registration") { ... }
}
...
}
fun NavGraphBuilder.loginGraph(navController: NavController) {
navigation(startDestination = "username", route = "login") {
composable("username") { ... }
composable("password") { ... }
composable("registration") { ... }
}
}
NavHost(navController, startDestination = "home") {
...
loginGraph(navController)
...
}
注意:需要
loginGraph是NavGraphBuilder的扩展函数
BottomNavigation
底部导航栏
// 依赖
dependencies {
implementation("androidx.compose.material:material:1.3.1")
}
@Composable
fun BottomNavigation(
modifier: Modifier = Modifier,
backgroundColor: Color = MaterialTheme.colors.primarySurface,
contentColor: Color = contentColorFor(backgroundColor),
elevation: Dp = BottomNavigationDefaults.Elevation,
content: @Composable RowScope.() -> Unit
): Unit
BottomNavigation 应该包含多个 BottomNavigationItems 项,每个导航项代表一个单一的目的地。
@Composable
fun ScaffoldDemo(){
var selectedItem by remember { mutableStateOf(0) }
val items = listOf("主页", "我喜欢的", "设置")
Scaffold(
topBar = {
TopAppBar(
title = {
Text("主页")
},
navigationIcon = {
IconButton(onClick = {
}) {
Icon(Icons.Filled.ArrowBack, null)
}
}
)
},
bottomBar = {
BottomNavigation {
items.forEachIndexed { index, item ->
BottomNavigationItem(
icon = { Icon(Icons.Filled.Favorite, contentDescription = null) },
label = { Text(item) },
selected = selectedItem == index,
onClick = { selectedItem = index }
)
}
}
}
){
}
}
上面的代码效果如图:
NavigationBar
出自 Meterial Design 3,功能与 BottomNavigation 类似;
NavigationBar 需要与 NavigationBarItem 搭配使用
@Composable
fun NavigationBar(
modifier: Modifier = Modifier,
containerColor: Color = NavigationBarDefaults.containerColor,
contentColor: Color = MaterialTheme.colorScheme.contentColorFor(containerColor),
tonalElevation: Dp = NavigationBarDefaults.Elevation,
windowInsets: WindowInsets = NavigationBarDefaults.windowInsets,
content: @Composable RowScope.() -> Unit
) { ... }
@Composable
fun RowScope.NavigationBarItem(
selected: Boolean,
onClick: () -> Unit,
icon: @Composable () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
label: @Composable (() -> Unit)? = null,
alwaysShowLabel: Boolean = true,
colors: NavigationBarItemColors = NavigationBarItemDefaults.colors(),
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
) { ... }
NavigationDrawer
这部分的介绍请查看# Compose NavigationDrawer 和 响应式 UI 导航适配;写的很详细,简介易懂!
文章参考: