Android 学习之 Navigation导航

发布于:2025-04-10 ⋅ 阅读:(38) ⋅ 点赞:(0)

1. Navigation 介绍

Navigation 组件 是 Android Jetpack 的一部分,用于简化应用内导航逻辑,支持 Fragment、Activity 和 Compose 之间的跳转。核心优势:

  • 单 Activity 架构:减少 Activity 冗余,通过 Fragment 或 Compose 实现界面切换。
  • 可视化导航图:通过 XML 或代码声明页面跳转关系。
  • 统一返回栈管理:自动处理返回按钮和手势导航。
  • 类型安全的参数传递:通过 Safe Args 插件或 Compose 的 Route 实现。

2. 项目中导入 Navigation 插件

传统 View 项目 (XML + Fragment)

build.gradle (Module) 中添加依赖:

dependencies {
    def nav_version = "2.7.7"
    implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
    implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
    // Safe Args 插件(可选)
    classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
}

Compose 项目

build.gradle (Module) 中添加:

dependencies {
    def nav_version = "2.7.7"
    implementation "androidx.navigation:navigation-compose:$nav_version"
}

在导入时要尤其注意导入的是 navigation-fragment 还是 navigation-compose 两者的使用有很大区别


3. 实现 Navigation 导航(XML + Fragment)

步骤 1:创建导航图

res/navigation 目录下新建 nav_graph.xml

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/nav_graph"
    app:startDestination="@id/homeFragment">

    <fragment
        android:id="@+id/homeFragment"
        android:name="com.example.HomeFragment"
        android:label="Home">
        <action
            android:id="@+id/action_to_detail"
            app:destination="@id/detailFragment" />
    </fragment>

    <fragment
        android:id="@+id/detailFragment"
        android:name="com.example.DetailFragment"
        android:label="Detail" />
</navigation>

其中的action属于显式定义动作,如果是跳转页面的话可以不显式定义,但如果使用androidx.navigation:safe-args插件必须定义动作才能生成参数类。

步骤 2:配置 NavHost

在 Activity 的布局中添加 NavHostFragment

<androidx.fragment.app.FragmentContainerView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/nav_host_fragment"
    android:name="androidx.navigation.fragment.NavHostFragment"
    app:navGraph="@navigation/nav_graph"
    app:defaultNavHost="true" />

步骤 3:执行导航操作

在 Fragment 中通过 NavController 跳转:

// HomeFragment.kt
button.setOnClickListener {
    findNavController().navigate(R.id.action_to_detail)
}

参数传递(Safe Args)

  1. nav_graph.xml 中定义参数:
<fragment android:id="@+id/detailFragment">
    <argument
        android:name="userId"
        app:argType="string" />
</fragment>
  1. 通过 Safe Args 传递参数:
val action = HomeFragmentDirections.actionToDetail("user123")
findNavController().navigate(action)

4. 基于 Compose 的 Navigation 导航

步骤 1:定义路由

在 Compose 中通过字符串定义路由:

object Routes {
    const val HOME = "home"
    const val DETAIL = "detail/{userId}"
}

步骤 2:配置 NavController

MainActivity 中初始化导航控制器:

val navController = rememberNavController()
NavHost(navController = navController, startDestination = Routes.HOME) {
    composable(Routes.HOME) {
        HomeScreen(onNavigateToDetail = { userId ->
            navController.navigate("detail/$userId")
        })
    }
    composable(Routes.DETAIL) { backStackEntry ->
        val userId = backStackEntry.arguments?.getString("userId")
        DetailScreen(userId = userId)
    }
}

步骤 3:触发导航

HomeScreen 中点击按钮跳转:

Button(onClick = { onNavigateToDetail("user123") }) {
    Text("Go to Detail")
}

5. 总结

  • 传统 XML 方式:适合已有 Fragment 项目,通过可视化导航图管理跳转逻辑。
  • Compose 方式:声明式 API,更适合现代化 Compose 项目,路由管理更灵活。
  • 核心优点:统一导航逻辑、类型安全、简化返回栈管理。

推荐场景

  • 新项目优先使用 Compose Navigation。
  • 旧项目逐步迁移时,可混合使用 XML 和 Compose 导航。

6. 拓展

1. 多模块导航

核心场景
  • 大型项目中按功能拆分模块(如登录模块、支付模块、个人中心模块)。
  • 模块间解耦,允许独立开发和测试。
实现方案
步骤 1:创建子模块
  1. 新建模块

    • 在 Android Studio 中右键项目 → New → Module → 选择 Phone & Tablet ModuleAndroid Library
    • 命名模块(如 feature-auth),包名建议为 com.example.feature.auth
  2. 模块结构

    project-root/
      |- app/                  // 主模块
      |- feature-auth/         // 子模块
          |- src/main/
              |- res/navigation/auth_nav_graph.xml  // 子模块导航图
    
步骤 2:子模块定义导航图

在子模块中创建独立的导航图:

<!-- feature-auth/res/navigation/auth_nav_graph.xml -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/auth_nav_graph"
    app:startDestination="@id/loginFragment">

    <fragment
        android:id="@+id/loginFragment"
        android:name="com.example.feature.auth.LoginFragment">
        <action
            android:id="@+id/action_to_register"
            app:destination="@id/registerFragment" />
    </fragment>
    <fragment android:id="@+id/registerFragment" ... />
</navigation>
步骤 3:主模块集成子模块
  1. 添加依赖
    在主模块的 build.gradle 中引入子模块:

    dependencies {
        implementation project(":feature-auth")
    }
    
  2. 合并导航图
    在主导航图中通过 <include> 引入子模块导航图:

    <!-- app/res/navigation/main_nav_graph.xml -->
    <navigation ...>
        <fragment android:id="@+id/homeFragment">
            <action
                android:id="@+id/action_to_auth"
                app:destination="@id/auth_nav_graph" />
        </fragment>
        <include app:graph="@navigation/auth_nav_graph" />  <!-- 关键 -->
    </navigation>
    
  3. 跨模块跳转
    直接使用子模块的 Action ID 或 Destination ID:

    // 从主模块跳转到子模块的登录页
    findNavController().navigate(R.id.action_to_auth)
    
参数传递
  1. 子模块定义参数

    <!-- auth_nav_graph.xml -->
    <fragment android:id="@+id/registerFragment">
        <argument android:name="email" app:argType="string" />
    </fragment>
    
  2. 主模块传递参数

    // 使用生成的 Directions 类(需主模块依赖子模块的 Safe Args)
    val directions = LoginFragmentDirections.actionToRegister(email = "user@example.com")
    findNavController().navigate(directions)
    
注意事项
  • 资源冲突:子模块资源需添加前缀(如 auth_),避免与主模块重复。
  • 依赖版本一致:主模块和子模块的 Navigation 版本必须相同。
  • ProGuard 规则:保留子模块生成的导航类。

2. 深层链接(DeepLink)

核心场景
  • 从外部链接直接跳转到应用内指定页面(如邮件中的订单详情链接)。
  • Web 页面与应用页面对接。
实现方案
XML + Fragment 方式
  1. 在导航图中定义 DeepLink

    <fragment android:id="@+id/detailFragment">
        <deepLink app:uri="example://app/detail/{id}" />  <!-- 定义 URI 模板 -->
    </fragment>
    
  2. 配置 AndroidManifest
    <activity> 标签内添加 Intent Filter:

    <activity ...>
        <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="example"
                android:host="app"
                android:pathPrefix="/detail" />
        </intent-filter>
    </activity>
    
  3. 处理 DeepLink 参数

    class DetailFragment : Fragment() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            arguments?.let {
                val id = it.getString("id")  // 从 URI 中提取参数
            }
        }
    }
    
Compose 方式
  1. 定义 DeepLink 路由

    composable(
        route = "detail/{id}",
        deepLinks = listOf(
            navDeepLink { uriPattern = "example://app/detail/{id}" }
        )
    ) { backStackEntry ->
        val id = backStackEntry.arguments?.getString("id")
        DetailScreen(id = id)
    }
    
  2. 触发 DeepLink

    // 通过 URI 跳转
    val request = NavDeepLinkRequest.Builder
        .fromUri("example://app/detail/123".toUri())
        .build()
    navController.navigate(request)
    
测试 DeepLink

使用 ADB 命令模拟 DeepLink:

adb shell am start -d "example://app/detail/123" com.example.app
注意事项
  • 路径匹配:URI 的 pathPattern 需与导航图中定义一致。
  • 安全性:验证外部传入参数,避免恶意数据注入。
  • 多模块 DeepLink:子模块的 DeepLink 需在主模块的 AndroidManifest 中声明。

总结

  • 多模块导航:通过模块拆分和 <include> 标签整合导航图,实现功能解耦和代码复用。
  • 深层链接:通过 URI 映射应用内页面,提升用户体验和外部系统集成能力。

推荐实践

  • 在电商应用中,订单模块可独立为子模块,通过 DeepLink 实现从推送通知直接跳转订单详情。
  • 社交应用中将个人主页模块化,通过 feature-profile 子模块管理,支持外部链接直达用户主页。

GitHub 多模块示例 | DeepLink 官方指南