一、从零搭建第一个Navigation项目

好,咱们直接动手。这一章我会带你完整创建一个基于Navigation组件的项目。说白了,就是让你亲眼看到Activity、Fragment、NavHostFragment和导航图是怎么串起来的。

我记得刚开始接触Navigation时,总觉得这玩意儿有点玄乎。后来在项目里踩了几次坑,才真正理解它的设计哲学。今天咱们就一步步来,把每个环节都讲透。

1.1 创建Activity——单Activity架构的起点

先创建一个普通的Android项目。我个人习惯用Empty Activity模板,然后把默认的Activity改造成我们的「单Activity容器」。

为什么要单Activity?你想想看,传统的多Activity架构,每个页面切换都要经历Activity生命周期,不仅慢,而且数据传递特别麻烦。Navigation组件就是来解决这个问题的——一个Activity,多个Fragment,通过导航图统一管理。

来看代码:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

嗯,这里要注意:Activity的布局文件里,我们只需要放一个NavHostFragment。它就像个占位符,所有Fragment都会在这个位置切换。

activity_main.xml长这样:

<?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">

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

</androidx.constraintlayout.widget.ConstraintLayout>
⚠️ 避坑提醒: 我曾经在项目里忘记设置 app:defaultNavHost="true",结果按返回键时Fragment不响应。这个属性告诉系统:当前Activity的返回键由Navigation组件接管。少了它,你的返回逻辑就会出问题。

1.2 创建NavHostFragment——导航的容器

其实上面我们已经把NavHostFragment放进布局了。但这里有个关键点:android:name属性必须写全类名androidx.navigation.fragment.NavHostFragment,不能简写。

NavHostFragment的作用是什么?说白了,它就是一个Fragment的「容器+调度器」。你告诉它要去哪个Fragment,它就帮你执行切换动画、管理返回栈、传递参数。

我个人习惯把NavHostFragment放在布局的最外层,这样整个屏幕都是导航区域。当然你也可以把它放在某个局部区域,实现「页面内嵌导航」的效果。

1.3 创建Fragment——导航的页面单元

接下来创建两个Fragment,作为导航的目标页面。咱们就叫HomeFragment和DetailFragment。

class HomeFragment : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_home, container, false)
    }
}

class DetailFragment : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_detail, container, false)
    }
}

每个Fragment对应一个布局文件。这里我建议你给Fragment命名时带上业务含义,别用Fragment1、Fragment2这种名字。我在项目里见过有人这么干,三个月后自己都分不清哪个是哪个。

1.4 编写导航图——串联一切的蓝图

导航图是Navigation组件的核心。它用XML描述所有Fragment之间的跳转关系。你想想看,以前我们写Intent跳转Activity,跳转逻辑散落在各个Activity里。现在好了,所有跳转关系集中在一个文件里管理。

res/navigation/目录下创建nav_graph.xml

<?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.navigationdemo.HomeFragment"
        android:label="首页"
        tools:layout="@layout/fragment_home">
        <action
            android:id="@+id/action_home_to_detail"
            app:destination="@id/detailFragment" />
    </fragment>

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

</navigation>
🔑 关键点解析:
  • app:startDestination:指定应用启动时默认显示的Fragment。这里设为homeFragment。
  • <action>:定义跳转动作。每个action都有一个唯一id,代码里通过这个id触发跳转。
  • tools:layout:方便在Android Studio的导航编辑器中预览布局。

1.5 在Fragment中触发导航

导航图写好了,怎么在代码里跳转?很简单,通过Navigation.findNavController()获取NavController,然后调用navigate()方法。

class HomeFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        view.findViewById<Button>(R.id.btnGoDetail).setOnClickListener {
            // 通过导航图定义的action跳转
            Navigation.findNavController(it)
                .navigate(R.id.action_home_to_detail)
        }
    }
}

这里有个小细节:findNavController()的参数可以是View,也可以是Fragment。我个人习惯传View,因为View一定在当前的Fragment层级内,不容易出错。

💡 经验之谈: 我曾经在项目里遇到一个bug——点击按钮没反应。排查了半天,发现是Fragment的布局里嵌套了多层ViewGroup,导致findNavController()找不到NavHostFragment。解决方案是确保调用navigate()的View属于NavHostFragment管理的Fragment层级。

1.6 完整项目结构一览

到这里,一个完整的Navigation项目就搭建好了。咱们用一张图来梳理整个架构:

Navigation 项目架构图 MainActivity(单Activity容器) setContentView(R.layout.activity_main) NavHostFragment(导航容器) app:navGraph="@navigation/nav_graph" HomeFragment startDestination action_home_to_detail → DetailFragment 目标Fragment 接收参数 图例: Activity层 导航容器 Fragment页面

1.7 运行验证

把项目跑起来,你应该能看到HomeFragment的内容。点击按钮,页面平滑切换到DetailFragment。按返回键,又回到HomeFragment。

这就是Navigation组件最基础的用法。你想想看,整个过程我们只创建了一个Activity,所有页面切换都在Fragment层面完成。这就是单Activity架构的核心思想。

✅ 本章核心要点:
  • 一个Activity作为容器,承载所有Fragment
  • NavHostFragment是导航的「大脑」,负责Fragment的切换和管理
  • 导航图(nav_graph.xml)集中管理所有跳转关系
  • 通过Navigation.findNavController().navigate()触发跳转

嗯,到这里第一个Navigation项目就创建完成了。下一章咱们会深入讲解导航图的更多配置,包括参数传递、动画设置、深层链接等高级用法。但今天先把基础打牢,你亲手敲一遍代码,感受一下Navigation组件带来的便利。


公众号:蓝海资料掘金营,微信deep3321