Android 中Navigation的使用

一、Navigation 是什么

Jetpack Navigation Component 是官方用来管理 页面跳转(Fragment / Activity / Dialog) 的框架。核心作用:

  • 统一管理跳转逻辑
  • 自动处理返回栈
  • 支持安全参数传递(Safe Args)
  • 支持深度链接、动画、嵌套导航
  • 配合单 Activity 架构,减少 Activity 数量

二、核心三要素

  1. NavHost
  • 布局里的 FragmentContainerView
  • 作用:承载所有 Fragment 的容器
  • 必须指定 navGraphdefaultNavHost="true"
  1. NavController
  • 控制跳转、返回、切换页面的控制器
  • 页面跳转的控制器
  • 通过 findNavController() 获取
  • 负责:跳转、返回、返回栈、监听切换
  1. NavGraph
  • XML 资源文件,定义所有页面、跳转关系、参数、动画
  • 位置:res/navigation/xxx.xml
  • 作用:定义所有页面、跳转路线、参数、动画
  • 就像一张APP 路线地图

三、基础使用步骤(最常用)

1. 依赖(build.gradle)

arduino 复制代码
// 导航
implementation "androidx.navigation:navigation-fragment-ktx:2.7.7"
implementation "androidx.navigation:navigation-ui-ktx:2.7.7"

xml

ini 复制代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">

<!-- 容器:必须用 FragmentContainerView,fragment淘汰了 -->
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host"
android:layout_width="match_parent"
android:layout_height="match_parent"
<!-- 固定写 NavHostFragment -->
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />

</androidx.constraintlayout.widget.ConstraintLayout>
  • defaultNavHost="true":自动绑定系统返回键
  • navGraph 指向导航图
ini 复制代码
<?xml version="1.0" encoding="utf-8"?>
<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/nav_graph"
app:startDestination="@id/homeFragment">

<!-- 首页 -->
<fragment
android:id="@+id/homeFragment"
android:name="com.example.demo.HomeFragment"
android:label="首页"
tools:layout="@layout/fragment_home">

<action
android:id="@+id/action_home_to_detail"
app:destination="@id/detailFragment"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right"
app:launchSingleTop="true"/>

</fragment>

<!-- 详情页 -->
<fragment
android:id="@+id/detailFragment"
android:name="com.example.demo.DetailFragment"
android:label="详情"
tools:layout="@layout/fragment_detail">

<!-- 参数 -->
<argument
android:name="userId"
app:argType="integer"
android:defaultValue="0" />

<argument
android:name="userName"
app:argType="string"
android:defaultValue="未知" />

</fragment>

</navigation>
1. 直接使用 Action ID(最简单)
arduino 复制代码
// 在 Fragment 中 findNavController().navigate(R.id.action_home_to_detail)
  • 优点:最简单,直接。
  • 缺点:不能传递参数。
2. 使用 Directions 类(类型安全,强烈推荐)
scss 复制代码
findNavController().navigate(
VideoFragmentDirections.actionHomeToDetail()
)
  • 优点:类型安全,参数自动生成,不会写错。
  • 缺点:需要配置 Safe Args。
3. 使用 Bundle 传参(原始方式)
scss 复制代码
val bundle = bundleOf("key" to "value")
findNavController().navigate(R.id.action_home_to_detail, bundle)
  • 优点:灵活,不需要 Safe Args。

  • 缺点:不安全,容易写错 key 或类型。

kotlin

scss 复制代码
val navOptions = NavOptions.Builder()
    .setEnterAnim(R.anim.slide_in_right)
    .setExitAnim(R.anim.slide_out_left)
    .setPopUpTo(R.id.homeFragment, true)
    .build()

findNavController().navigate(R.id.action_home_to_detail, null, navOptions)
  • 优点:可以在代码中动态设置动画、返回栈行为等,比 XML 更灵活。
  • 缺点:代码稍多。

kotlin

scss 复制代码
val uri = Uri.parse("app://myapp.com/myFragment")
findNavController().navigate(uri)
  • 优点:可以从通知、网页、其他应用直接跳转到 APP 内的指定页面。
  • 缺点:需要配置深度链接。
一句话总结对比
方式 优点 缺点 适用场景
Action ID 最简单 不能传参 简单跳转
Directions 类型安全、推荐 需要配置 带参数跳转
Bundle 灵活 不安全 简单参数、无 Safe Args
NavOptions 最灵活、动态配置 代码多 动态设置动画 / 返回栈
DeepLink 外部跳转 配置复杂 通知、网页跳转

日常开发首选:Directions 类(类型安全)

动态配置首选:NavOptions

外部跳转首选:DeepLink

5. 返回

scss 复制代码
findNavController().popBackStack()

四、参数传递(重点)

1. 普通方式(不推荐)

kotlin

scss 复制代码
val bundle = bundleOf("id" to 123)
navigate(R.id.action_home_to_detail, bundle)

2. Safe Args 安全方式(推荐)

配置插件

gradle

bash 复制代码
id 'androidx.navigation.safeargs.kotlin' version '2.7.7' apply false

在模块 build.gradle:

gradle

bash 复制代码
id 'androidx.navigation.safeargs.kotlin'

xml

ini 复制代码
!-- 详情页 -->
<fragment
android:id="@+id/detailFragment"
android:name="com.example.demo.DetailFragment"
android:label="详情"
tools:layout="@layout/fragment_detail">

<!-- 参数 -->
<argument
android:name="userId"
app:argType="integer"
android:defaultValue="0" />

<argument
android:name="userName"
app:argType="string"
android:defaultValue="未知" />

</fragment>

跳转

kotlin

kotlin 复制代码
class HomeFragment : Fragment() {

    private lateinit var binding: FragmentHomeBinding

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        binding = FragmentHomeBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        binding.btnGoDetail.setOnClickListener {
            // Safe Args 跳转(类型安全)
            val direction = HomeFragmentDirections.actionHomeToDetail(
                userId = 10086,
                userName = "ZhangSan"
            )
            findNavController().navigate(direction)
        }
    }
}

接收

kotlin 复制代码
class DetailFragment : Fragment() {

    private lateinit var binding: FragmentDetailBinding

    // Safe Args 接收参数
    private val args: DetailFragmentArgs by navArgs()

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        binding = FragmentDetailBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // 获取参数
        val userId = args.userId
        val userName = args.userName
        binding.tvInfo.text = "ID:$userId \n姓名:$userName"

        // 返回
        binding.btnBack.setOnClickListener {
            findNavController().popBackStack()
        }
    }
}

优点:类型安全、不会空指针、不会写错 key


五、跳转动画

xml

ini 复制代码
<action
    android:id="@+id/action_home_to_detail"
    app:destination="@id/detailFragment"
    app:enterAnim="@anim/slide_in_right"
    app:exitAnim="@anim/slide_out_left"
    app:popEnterAnim="@anim/slide_in_left"
    app:popExitAnim="@anim/slide_out_right" />

六、返回栈控制(高频面试)

1. 清除返回栈

xml

ini 复制代码
//登录成功 → 跳主页,再也回不去登录页
app:popUpTo="@id/loginFragment" 
app:popUpToInclusive="true"

<action
android:id="@+id/action_login_to_main"
app:destination="@id/mainFragment"
app:popUpTo="@id/loginFragment"
app:popUpToInclusive="true"/>
  • popUpTo:回退到某个页面(就是清理到某个页面)
  • popUpToInclusive="true":把该页面也弹出(为true的话,把loginFragment也清理掉,false不清理)
  • 效果:登录→主页,按返回直接退出 APP,不回登录页。

常用于:登录 → 主页,按返回不回到登录页

2. singleTop 模式

xml

ini 复制代码
app:launchSingleTop="true"

避免重复打开同一个页面

3.监听页面切换

kotlin

erlang 复制代码
findNavController().addOnDestinationChangedListener { controller, destination, arguments ->
    when (destination.id) {
        R.id.homeFragment -> Log.d("nav", "首页")
        R.id.detailFragment -> Log.d("nav", "详情页")
    }
}

4.返回到指定页面

kotlin

scss 复制代码
//返回上一页
findNavController().popBackStack()
//返回到首页,把中间页清理掉
findNavController().popBackStack(R.id.homeFragment, false)

5.配合 BottomNavigationView

kotlin

ini 复制代码
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host) as NavHostFragment
val navController = navHostFragment.navController
binding.bottomNav.setupWithNavController(navController)

七、Activity 之间跳转

xml

ini 复制代码
<activity
    android:id="@+id/settingActivity"
    android:name="com.xxx.SettingActivity" />

kotlin

scss 复制代码
navigate(R.id.settingActivity)

八、Dialog 跳转

xml

ini 复制代码
<dialog
    android:id="@+id/confirmDialog"
    android:name="com.xxx.ConfirmDialog" />

适合模块化、多模块项目

xml

ini 复制代码
<include app:graph="@navigation/user_nav" />

kotlin

ini 复制代码
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host) as NavHostFragment
val navController = navHostFragment.navController
binding.bottomNav.setupWithNavController(navController)

十一、常用 API 总结

kotlin

scss 复制代码
// 获取控制器
findNavController()
// 跳转
navigate(resId)
// 返回到上一页
popBackStack()
// 返回到指定页面
popBackStack(resId, inclusive = false)
// 获取参数
val args by navArgs<T>()
// 监听页面切换
navController.addOnDestinationChangedListener { _, destination, _ -> }

十二、高频面试点

  1. Navigation 底层是什么?→ 事务操作 Fragment,维护一个 堆栈
  2. Safe Args 为什么安全?→ 编译时生成类,类型安全,避免 Key 错误
  3. popUpTo 和 popUpToInclusive 区别?→ 前者回退到节点,后者是否包含该节点一起弹出
  4. 如何处理重复跳转?→ launchSingleTop="true"
  5. 如何和 ViewModel 配合?→ 使用 navGraphViewModels() 共享同导航图内的 ViewModel

十三、最实用一句话总结

  • 单 Activity + 多 Fragment 架构首选

  • 跳转用 action,参数用 Safe Args

  • 返回栈用 popUpTo 管理

  • 底部导航、Drawer 都能一键绑定

  • 一定要用 FragmentContainerView,不要用 标签

  • defaultNavHost="true" 才能响应返回键

  • 不要在 onCreate 里获取 navController,会空指针

  • 重复跳转崩溃 → 加 launchSingleTop

  • 参数为空崩溃 → 用 Safe Args 加默认值

相关推荐
simplepeng4 天前
再见 PredictiveBackHandler:如何迁移到 Compose 中的新导航事件
android jetpack
alexhilton4 天前
在Compose中用Shader实现透明的粘稠元球效果
android·kotlin·android jetpack
ljt27249606617 天前
Compose笔记(七十四)--BlurMaskFilter
笔记·android jetpack
ljt27249606617 天前
Compose笔记(七十五)--withFrameNanos
笔记·android jetpack
hnlgzb8 天前
请详细解释一下MVVM这个设计模型
android·kotlin·android jetpack·compose
hnlgzb10 天前
目前编写安卓app的话有哪几种设计模式?
android·设计模式·kotlin·android jetpack·compose
png11 天前
从零开始Compose天气预报(完结)
android jetpack
阿巴斯甜11 天前
produceState的使用:
android jetpack
阿巴斯甜11 天前
snapshotFlow的使用
android jetpack