mcpField
N
navigation3

navigation3

github.com/new-silvermoon开发1.0k106 次安装15 周前更新
代码仓库

关于 navigation3

这是一项使用 Navigation 3 在 Jetpack Compose 中实现状态驱动导航的技能,适用于 Android 开发者。内容涵盖返回栈、ViewModel 作用域、自适应布局、深链、状态恢复、动画、测试及从 Navigation Compose 迁移。

主要功能

  1. 01可序列化的导航键
  2. 02可变状态返回栈
  3. 03用于 ViewModel 的条目装饰器
  4. 04自适应布局与深链
  5. 05状态恢复与测试

适用场景

  1. 01将目标页面建模为应用状态
  2. 02为导航条目限定 ViewModel 作用域
  3. 03适配手机、平板和折叠设备布局
  4. 04测试返回栈变化与屏幕渲染

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/new-silvermoon/awesome-android-agent-skills --skill navigation3

安装后在 Claude Code 中输入/navigation3确认已加载。

详细介绍

Navigation 3

概览

使用 Navigation 3 在 Jetpack Compose 中实现状态驱动的导航。与 Navigation Compose 不同,Navigation 3 将导航建模为应用状态,而不是通过 NavController 实现。本指南涵盖导航键、返回栈管理、ViewModel 作用域、条目装饰器、自适应布局、深层链接、动画、状态恢复和测试。

设置

添加 Navigation 3 依赖项:

// build.gradle.kts
dependencies {

    implementation("androidx.navigation3:navigation3-runtime:1.0.0-alpha08")
    implementation("androidx.navigation3:navigation3-ui:1.0.0-alpha08")

    // Lifecycle integration
    implementation("androidx.lifecycle:lifecycle-viewmodel-navigation3:2.9.2")

    // Serialization
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.1")
}

// Enable serialization plugin
plugins {
    kotlin("plugin.serialization") version "2.2.0"
}

核心概念

1. 定义导航键

导航目标由不可变且可序列化的对象表示。

import kotlinx.serialization.Serializable

@Serializable
data object Home

@Serializable
data class Profile(
    val userId: String
)

@Serializable
data class Product(
    val productId: String,
    val showReviews: Boolean = false
)

@Serializable
data object Settings

键会成为你的导航状态。


2. 创建返回栈

Navigation 3 使用可变状态返回栈取代 NavController。

@Composable
fun MyApp() {

    val backStack = remember {
        mutableStateListOf<Any>(Home)
    }

    AppNavDisplay(backStack)
}

3. 创建 NavDisplay
@Composable
fun AppNavDisplay(
    backStack: SnapshotStateList<Any>
) {

    NavDisplay(
        backStack = backStack,
        onBack = {
            if (backStack.size > 1) {
                backStack.removeLast()
            }
        }
    ) { key ->

        when (key) {

            Home ->
                HomeScreen(
                    onProfileClick = {
                        backStack += Profile(it)
                    }
                )

            is Profile ->
                ProfileScreen(
                    userId = key.userId
                )

            is Product ->
                ProductScreen(
                    productId = key.productId,
                    showReviews = key.showReviews
                )

            Settings ->
                SettingsScreen()
        }
    }
}

导航模式

向前导航
backStack += Profile("user123")
返回
backStack.removeLast()
替换当前屏幕
backStack[backStack.lastIndex] = Home
清空返回栈
backStack.clear()
backStack += Home
返回根页面
while (backStack.size > 1) {
    backStack.removeLast()
}

参数处理

获取参数

参数已包含在导航键中。

when (val key = currentKey) {

    is Profile -> {
        ProfileScreen(
            userId = key.userId
        )
    }

    is Product -> {
        ProductScreen(
            productId = key.productId
        )
    }
}
传递 ID,而非对象
// CORRECT
backStack += Profile(user.id)

// Fetch object inside ViewModel
class ProfileViewModel(
    savedStateHandle: SavedStateHandle,
    repository: UserRepository
) : ViewModel() {

    val profile = savedStateHandle.toRoute<Profile>()

    val user =
        repository.getUser(profile.userId)
}
// INCORRECT

backStack += User(...)
backStack += ProductRepository(...)
backStack += ProductViewModel(...)

ViewModel 集成

Navigation 3 使用条目装饰器来限定 ViewModel 的作用域。

NavDisplay(

    backStack = backStack,

    entryDecorators = listOf(

        rememberSceneSetupNavEntryDecorator(),

        rememberSavedStateNavEntryDecorator(),

        rememberViewModelStoreNavEntryDecorator()
    )

) { key ->

    // destinations
}
ViewModel 示例
class ProfileViewModel(
    savedStateHandle: SavedStateHandle
) : ViewModel() {

    val profile =
        savedStateHandle.toRoute<Profile>()
}

条目装饰器

Navigation 3 使用装饰器来附加生命周期功能。

场景设置
rememberSceneSetupNavEntryDecorator()

为每个条目创建导航场景。

保存状态
rememberSavedStateNavEntryDecorator()

进程重建后自动恢复目标状态。

ViewModel 存储
rememberViewModelStoreNavEntryDecorator()

将 ViewModel 的作用域限定在各个导航条目中。


自适应导航

Navigation 3 可与 Material Adaptive 布局集成。

NavDisplay(

    backStack = backStack,

    sceneStrategy = rememberListDetailSceneStrategy()

)

使用自适应场景策略自动切换为:

  • 单窗格(手机)
  • 双窗格(平板电脑)
  • 折叠设备

深层链接

深层链接应解析为导航键。

fun handleDeepLink(uri: Uri) {

    val userId =
        uri.lastPathSegment ?: return

    backStack += Profile(userId)
}

避免手动构造路由字符串。


动画

导航过渡通过场景过渡定义。

NavDisplay(

    backStack = backStack,

    transitionSpec = {

        fadeIn() togetherWith fadeOut()

    }
)

Navigation 3 的动画 API 在 Alpha 阶段可能会有所变化。


状态恢复

导航键可序列化,并会自动恢复。

val backStack = rememberSaveable(
    saver = navBackStackSaver()
) {
    mutableStateListOf(Home)
}

始终确保键可序列化。


测试

导航由状态驱动,因此测试很简单。

示例
@Test
fun navigateToProfile() {

    val backStack =
        mutableStateListOf<Any>(Home)

    backStack += Profile("123")

    assertEquals(
        Profile("123"),
        backStack.last()
    )
}

Compose UI 测试可以通过检查当前返回栈来验证屏幕渲染情况。


从 Navigation Compose 迁移

Navigation ComposeNavigation 3
NavController可变返回栈
NavHostNavDisplay
navigate()backStack += Key
popBackStack()removeLast()
字符串路由可序列化的键
composable()when(key)
导航图状态驱动的目标

推荐的项目结构

navigation/

    AppNavigation.kt

    NavigationKeys.kt

    NavigationDisplay.kt

feature/

    home/

    profile/

    settings/

重要规则

应该
  • 使用不可变且可序列化的键
  • 将导航状态保留在 Compose 中
  • 传递 ID,而不是复杂对象
  • 使用条目装饰器限定 ViewModel 的作用域
  • 使用 rememberSaveable 恢复状态
  • 将导航建模为可观察的应用状态
不应该
  • 使用字符串路由
  • 通过导航传递仓库或 ViewModel
  • 修改导航键
  • 在返回栈中存储业务对象
  • 在重组时重新创建返回栈
  • 混用 Navigation Compose API 和 Navigation 3 API

参考资料

  • Android Navigation 3 文档
  • Navigation 3 示例
  • Lifecycle ViewModel Navigation 3 文档
  • Material 3 Adaptive Navigation 文档

评论

支持 Markdown