[Android官方] Jetpack导航组件:实战与分析

本文介绍了Android Jetpack的导航组件,包括其组成:导航图、导航容器和导航控制器。通过实例展示了如何使用Navigation组件进行Fragment的导航,封装事务、过场动画、参数传递,并提供了深入理解NavHostFragment、nav_graph.xml和NavController的分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

导航(Navigation )指的是用户在应用中的前进和后退操作。导航很大程度上和业务逻辑相关。但是,在传统的Android应用开发中,导航需要开发者自己处理,对应的backstack也需要开发者自行维护,与业务相关的导航逻辑也要硬编码在Java代码中。而同时期的iOS应用开发框架早就提出了导航图的概念,并对导航进行了框架级别的抽象和支持。

为了解决类似问题,简化Android应用开发,Google在2018年推出了JetpackJetpack是一套库、工具和指南,可帮助开发者更轻松地编写优质应用。在Jetpack中,Android第一次以Navigation组件的形式对用户的导航行为进行了抽象和封装,不仅简化了开发流程,还通过遵循一套既定原则来确保一致且可预测的用户体验,从而实现了代码可维护性用户体验一致性的完美结合。

概览

Jetpack导航组件由以下三个关键部分组成:

  • 导航图(Navigation graph):在一个XML文件中集中定义所有导航相关信息的,它包括应用内的所有导航目标以及所有可能的导航路径
  • 导航容器(NavHost):显示导航目标的容器。Jetpack提供了一个默认的导航容器实现NavHostFragment
  • 导航控制器(NavController):在导航容器中管理导航对象。当用户在应用中前进或者后退时,导航控制器决定下一个将要显示的内容。

功能

JetpackNavigation组件提供以下几个功能:

  • 封装Fragment事务:Navigation组件封装了Fragment事务,从而简化了开发者的工作。
  • 为过场动画提供标准化支持:开发者只需要专注于定义过场动画本身。
  • 传递参数:通过Safe Args插件,开发者可在导航目标之间传递类型安全的数据。

实战

我们的实战是基于Google的Jetpack Navigation Codelab。它的最终效果是这样:
在这里插入图片描述
这是3个简单的Fragment之间跳转的情景,经过过场动画的修饰,它们之前的切换非常流畅自然。接下来,我们就通过代码来详细讲解Fragment导航是如何实现的。

首先,在build.gradle中添加以下依赖:

dependencies {
    ...
    //Navigation
    implementation "androidx.navigation:navigation-fragment-ktx:$rootProject.navigationVersion"
    implementation "androidx.navigation:navigation-ui-ktx:$rootProject.navigationVersion"
}

新建两个Fragment。其中,第二个Fragment会根据传入的参数决定要加载的UI:

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

class FlowStepFragment : Fragment() {
  override fun onCreateView(
      inflater: LayoutInflater,
      container: ViewGroup?,
      savedInstanceState: Bundle?
  ): View? {
    return when (flowStepNumber) {
        2 -> inflater.inflate(R.layout.flow_step_two_fragment, container, false)
        else -> inflater.inflate(R.layout.flow_step_one_fragment, container, false)
    }
  }
}

新建导航视图文件(mobile_navigation.xml)
在这里插入图片描述
打开导航视图文件,进行可视化编辑,包括新增Fragment,或者连接Fragment:在这里插入图片描述
我们打开导航视图文件的Text标签,进入XML的编辑页面,并进行如下配置:

<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"
    app:startDestination="@+id/home_dest">
    <fragment
        android:id="@+id/home_dest"
        android:name="com.example.android.codelabs.navigation.HomeFragment"
        android:label="@string/home"
        tools:layout="@layout/home_fragment">
        <action
            android:id="@+id/next_action"
            app:destination="@+id/flow_step_one_dest"
            app:enterAnim="@anim/slide_in_right"
            app:exitAnim="@anim/slide_out_left"
            app:popEnterAnim="@anim/slide_in_left"
            app:popExitAnim="@anim/slide_out_right" />
    </fragment>
    <fragment
        android:id="@+id/flow_step_one_dest"
        android:name="com.example.android.codelabs.navigation.FlowStepFragment"
        tools:layout="@layout/flow_step_one_fragment">
        <argument
            android:name="flowStepNumber"
            app:argType="integer"
            android:defaultValue="1"/>
        <action
            android:id="@+id/next_action"
            app:destination="@+id/flow_step_two_dest">
        </action>
    </fragment>
    <fragment
        android:id="@+id/flow_step_two_dest"
        android:name="com.example.android.codelabs.navigation.FlowStepFragment"
        tools:layout="@layout/flow_step_two_fragment">
        <argument
            android:name="flowStepNumber"
            app:argType="integer"
            android:defaultValue="2"/>
        <action
            android:id="@+id/next_action"
            app:popUpTo="@id/home_dest">
        </action>
    </fragment>
</navigation>

在导航视图文件中,我们定义了三个Fragment。每一个Fragment标签下都定义了一个Action,用来指定下一个要显示的Fragment,我们还可以为Action指定过场动画。

此外,我们需要在Activity的布局文件加入导航容器NavHostFragment:

<androidx.drawerlayout.widget.DrawerLayout
    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/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.android.codelabs.navigation.MainActivity">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        ...
        <fragment
            android:id="@+id/my_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/mobile_navigation" />
    </LinearLayout>
    ...
</androidx.drawerlayout.widget.DrawerLayout>

然后,我们需要在Activity中添加如下代码:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.navigation_activity)
    }    
    
    override fun onSupportNavigateUp(): Boolean {
        return findNavController(R.id.my_nav_host_fragment).navigateUp()
    }
}

这里,我们重载了AppCompatActivity的onSupportNavigateUp方法。当用户点击导航栏内的向上按钮时,onSupportNavigateUp就会被调用,用来结束当前Activity,并显示父Activity。

最后,我们需要配置不同Fragment对应的跳转事件:

class HomeFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        val options = navOptions {
            anim {
                enter = R.anim.slide_in_right
                exit = R.anim.slide_out_left
                popEnter = R.anim.slide_in_left
                popExit = R.anim.slide_out_right
            }
        }
        
        view.findViewById<Button>(R.id.navigate_destination_button)?.setOnClickListener {
            findNavController().navigate(R.id.flow_step_one_dest, null, options)
        }
}

这里我们为HomeFragment中的navigate_destination_button添加了一个OnClickListener,并且指定按钮点击后要跳转到flow_step_one_dest对应的Fragment(定义在mobile_navigation.xml中)。同时,我们还为这个Action定义了过场动画。

以上的实现存在一个问题,那就是我们并没有利用已经预先定义在mobile_navigation.xml中的Action。实际上,更加优雅的实现是利用Android Studio插件自动生成的Action类来实现导航,代码如下:

class HomeFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        view.findViewById<Button>(R.id.navigate_destination_button)?.setOnClickListener {
            findNavController().navigate(HomeFragmentDirections.nextAction(1))
        }
}

这里的HomeFragmentDirections是Android Studio插件根据mobile_navigation.xml自动生成的类,它包括了参数和过场动画的设置。直接使用HomeFragmentDirections可以避免重复定义过场动画。

我们可以用类似的方法为FlowStepFragment配置跳转事件:

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

        view.findViewById<View>(R.id.next_button).setOnClickListener(
            Navigation.createNavigateOnClickListener(R.id.next_action)
        )
    }
}

这段代码的神奇之处就在于我们为flow_step_one_dest和flow_step_two_dest指定了相同的next_action。系统可以根据上下文,智能地推断导航目标!

可以看到,我们对于Fragment并非是通过原生的FragmentManager和FragmentTransaction进行控制。而是通过Navigation组件提供的API进行导航控制:

Navigation.findNavController(params).navigate(actionId)
Navigation.findNavController(params).navigate(action)
Navigation.createNavigateOnClickListener(actionId)

深入理解

通过上面的代码实战,我已经实现了Fragment的导航。但是,对于Navigation组件本身的设计思想和实现原理,我们并不了解。那么,接下来我们就对Navigation组件本身进行一些深入的分析。下面这张图显示了Navigation组件的架构。
在这里插入图片描述

NavHostFragment

NavHostFragment是对NavHost接口的实现。它是一个支持导航的Fragment容器。我们需要导航的这些Fragment都将显示在NavHostFragment上面。

NavHostFragment有两个两个重要的属性我们一定会用到:

app:defaultNavHost=“true”
app:navGraph="@navigation/nav_graph.xml"

defaultNavHost设置为true,意味着NavHostFragment将会提供默认的导航功能。具体来说,NavHostFragment会拦截并且处理系统back键的点击事件。当系统back键被点击后,NavHostFragment不会让当前Activity退出,取而代之的是根据配置切换Fragment。

navGraph这个属性用来指定导航图XML文件。NavHostFragment会根据这个文件的配置进行导航并展示对应的Fragment。

因此,我们需要在最顶层的Activity布局文件中添加NavHostFragment,并设置defaultNavHost和 navGraph属性。

nav_graph.xml

在代码实战环节,我们已经编辑过nav_graph.xml文件了。但是其中有一个很重要的属性我们没有讲解,那就是startDestination。

app:startDestination="@id/home_dest"

startDestination申明了哪一个导航目标会被作为默认布局加载到Activity中。这也就说明了,为什么我们的例子会默认显示HomeFragment。

NavController

NavController重要职责是:

  • 解析nav_graph.xml,并在内存中维护一个全局导航图
  • 维护Fragment的backstack,确保用户后退时显示正确的Fragment
  • 处理导航行为并显示对应的Fragment

总结

通过以上代码实战和深入分析,我们已经从实践和理论两个方面学习了Navigation组件。我强烈建议 读者们自己动手,亲自尝试使用这个组件,并把它应用到实际项目中去。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值