(已弃用) 学习 Jetpack Navigation

1. 开始之前

Navigation Architecture Component(导航架构组件)简化了导航的实现,同时还能帮助您可视化应用的导航流程。该库提供了许多优势,包括

  • 自动处理 fragment 事务
  • 默认情况下正确处理向上返回
  • 动画和过渡的默认行为
  • 将深度链接作为一流操作
  • 只需少量额外工作即可实现导航 UI 模式(如导航抽屉和底部导航)
  • 导航时传递信息的类型安全性
  • Android Studio 工具,用于可视化和编辑应用的导航流程

您将学习什么

在此 Codelab 中,您将使用下面显示的示例应用

6884c79d552f943.gif

所有 activity 和 fragment 均已为您创建。您将使用 Navigation Component 连接它们,并在此过程中实现以下功能:

  • 可视化导航图
  • 按目标和操作进行导航
  • 过渡动画
  • 菜单导航、底部导航和菜单抽屉导航
  • 类型安全的参数传递
  • 深度链接

前提条件

  • Kotlin 基础知识(此 Codelab 使用 Kotlin)
  • 最新稳定版 Android Studio,以及使用它的知识
  • 运行 API 19+ 的模拟器或设备

2. 入门

获取代码

从 GitHub 克隆导航 Codelab

$ git clone https://github.com/android/codelab-android-navigation

或者,您可以将仓库下载为 Zip 文件

获取最新稳定版 Android Studio

请确保您使用的是最新版 Android Studio。Android Studio 导航工具需要此版本。

如果需要下载最新版 Android Studio,可以在此处下载。

导航概览

Navigation Component 由三个协同工作的关键部分组成。它们是:

  1. 导航图(新的 XML 资源)- 此资源在一个集中位置包含所有与导航相关的信息。这包括应用中的所有位置(称为目标)以及用户可以通过应用采取的可能路径。
  2. NavHostFragment(布局 XML 视图)- 这是您添加到布局中的特殊微件。它显示导航图中的不同目标。
  3. NavController (Kotlin/Java 对象) - 此对象跟踪导航图中的当前位置。它在您遍历导航图时,协调在 NavHostFragment 中切换目标内容。

导航时,您将使用 NavController 对象,告知它您想去哪里或想在导航图中采取什么路径。NavController 随后将在 NavHostFragment 中显示适当的目标。

这就是基本概念。我们来从新的导航图资源开始,看看它在实践中是怎样的。

3. 导航图简介

目标

Navigation Component 引入了目标的概念。目标是您可以在应用中导航到的任何位置,通常是 fragment 或 activity。这些功能开箱即用,但如果需要,您还可以创建自己的自定义目标类型

导航图是一种新的资源类型,它定义了用户可以通过应用采取的所有可能路径。它直观地显示了从给定目标可以到达的所有目标。Android Studio 在其导航编辑器中显示该图。以下是您将为应用创建的起始导航图的一部分:

Home, Step One and Step Two

探索导航编辑器

  1. 打开 res/navigation/mobile_navigation.xml
  2. 点击 Design 进入设计模式

f01c48a365da45fc.png

您应该会看到以下内容:

92fe16a524095729.png

导航图显示了可用的目标。目标之间的箭头称为操作。稍后您将了解有关操作的更多信息。

  1. 点击一个目标以查看其属性。

4d58e392ee13a9ba.png

  1. 点击任何由箭头表示的操作以查看其属性。

903fa5df3721baf9.png

导航 XML 文件结构

您在图形导航编辑器中所做的所有更改都会改变底层的 XML 文件,这与布局编辑器修改布局 XML 的方式类似。

点击 Text 标签页

d5f69532f6f303f6.png

您将看到如下 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">

    <!-- ...tags for fragments and activities here -->

</navigation>

请注意:

  • <navigation> 是每个导航图的根节点。
  • <navigation> 包含一个或多个目标,由 <activity><fragment> 元素表示。
  • app:startDestination 是一个属性,它指定用户首次打开应用时默认启动的目标。

我们来看看一个 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
        .../>

    <action
        android:id="@+id/next_action"
        app:destination="@+id/flow_step_two_dest">
    </action>
</fragment>

请注意:

  • android:id 定义了 fragment 的 ID,您可以在此 XML 和代码中的其他地方引用该目标。
  • android:name 声明了导航到该目标时要实例化的 fragment 的完全限定类名。
  • tools:layout 指定了应在图形编辑器中显示的布局。

某些 <fragment> 标签还包含 <action><argument><deepLink>,我们稍后会介绍所有这些内容。

4. 将目标添加到导航图

示例应用在图中有几个目标。在本步骤中,您将添加一个全新的目标。您必须先将目标添加到导航图,然后才能导航到它。

  1. 打开 res/navigation/mobile_navigation.xml,然后点击 Design 标签页。
  2. 点击新建目标图标,然后选择“settings_fragment”

44a8212036929458.png

结果是一个新的目标,它在设计视图中渲染了 fragment 布局的预览。

e82a44431b77c6c1.png

请注意,您也可以直接编辑 XML 文件来添加目标

mobile_navigation.xml

<fragment
    android:id="@+id/settings_dest"
    android:name="com.example.android.codelabs.navigation.SettingsFragment"
    android:label="@string/settings"
    tools:layout="@layout/settings_fragment" />

5. 使用导航图进行导航

现在您已经有了这个很棒的导航图,但实际上您并没有使用它来导航。

Activity 和导航

Navigation Component 遵循导航原则中概述的指南。导航原则建议您使用 activity 作为应用的入口点。Activity 还将包含全局导航,例如底部导航、

相比之下,fragment 将是实际针对目标的布局。

为了使这一切正常工作,您需要修改 activity 布局以包含一个名为 NavHostFragment 的特殊微件。NavHostFragment 在您遍历导航图时,会换入和换出不同的 fragment 目标。

b85ea429c5ebad71.png

支持类似上图导航的简单布局如下所示。您可以在 res/layout-470dp/navigation_activity.xml 中找到此代码示例。

<LinearLayout
    .../>
    <androidx.appcompat.widget.Toolbar
        .../>
    <fragment
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:id="@+id/my_nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        app:navGraph="@navigation/mobile_navigation"
        app:defaultNavHost="true"
        />
    <com.google.android.material.bottomnavigation.BottomNavigationView
        .../>
</LinearLayout>

请注意:

  • 这是一个 activity 的布局。它包含全局导航,包括底部导航和工具栏
  • android:name="androidx.navigation.fragment.NavHostFragment"app:defaultNavHost="true" 将系统返回按钮连接到 NavHostFragment
  • app:navGraph="@navigation/mobile_navigation"NavHostFragment 与导航图关联起来。此导航图指定了用户在此 NavHostFragment 中可以导航到的所有目标。

最后,当用户执行点击按钮等操作时,您需要触发一个导航命令。一个名为 NavController 的特殊类会触发 NavHostFragment 中的 fragment 切换。

// Command to navigate to flow_step_one_dest
findNavController().navigate(R.id.flow_step_one_dest)

请注意,您会传入目标或操作 ID 进行导航。这些 ID 是在导航图 XML 中定义的。这是一个传入目标 ID 的示例。

NavController 功能强大,因为当您调用 navigate()popBackStack() 等方法时,它会根据您导航到或离开的目标类型,将这些命令转换为相应的框架操作。例如,当您使用 activity 目标调用 navigate() 时,NavController 会代表您调用 startActivity()

有几种方法可以获取与 NavHostFragment 关联的 NavController 对象。在 Kotlin 中,建议您使用以下扩展函数之一,具体取决于您是从 fragment、activity 还是 view 中调用导航命令:

轮到您使用 NavController 进行导航了。您将关联导航到目标按钮以导航到 flow_step_one_dest 目标(这是一个 FlowStepFragment 目标)

  1. 打开 HomeFragment.kt
  2. onViewCreated() 中关联 navigate_destination_button

HomeFragment.kt

val button = view.findViewById<Button>(R.id.navigate_destination_button)
button?.setOnClickListener {
    findNavController().navigate(R.id.flow_step_one_dest, null)
}
  1. 运行应用并点击导航到目标按钮。请注意,该按钮会导航到 flow_step_one_dest 目标。

点击监听器代码如下所示:

val button = view.findViewById<Button>(R.id.navigate_destination_button)
button?.setOnClickListener(
        Navigation.createNavigateOnClickListener(R.id.flow_step_one_dest, null)
)

6. 更改导航过渡

每次 navigate() 调用都关联有一个不太令人兴奋的默认过渡,如下所示:

From Home user clicks on Navigation to destination and navigates to step one.

默认过渡以及与调用相关的其他属性可以通过包含一组 NavOptions 来覆盖。NavOptions 使用 Builder 模式,允许您只覆盖和设置您需要的选项。还有一个 NavOptions 的 ktx DSL,这就是您将要使用的。

对于动画过渡,您可以在 anim 资源文件夹中定义 XML 动画资源,然后使用这些动画进行过渡。应用代码中包含一些示例。

409164bbcdea93ed.png

添加自定义过渡

更新代码,使按下导航到目标按钮时显示自定义过渡动画。

  1. 打开 HomeFragment.kt
  2. 定义一个 NavOptions 并将其传递给 navigate() 调用,导航到 navigate_destination_button
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)
}
  1. 如果步骤 5 中添加的代码还在,请将其删除
  2. 验证轻触导航到目标按钮会使 fragment 滑入屏幕,而按下返回按钮会使 fragment 滑出屏幕。

dcb9d8241c6647eb.gif

7. 使用操作进行导航

操作

导航系统还允许您通过操作进行导航。如前所述,导航图中显示的线条是操作的视觉表示。

bc86b406d6a75be0.png

通过操作进行导航相比按目标进行导航具有以下优势:

  • 您可以可视化应用中的导航路径
  • 操作可以包含您可以设置的其他关联属性,例如过渡动画、参数值和返回堆栈行为
  • 您可以使用插件 safe args 进行导航,稍后您将看到。

以下是连接 flow_step_one_destflow_step_two_dest 的操作的视觉表示和 XML:

2ab28ff13c759c17.png

<fragment
    android:id="@+id/flow_step_one_dest"
    android:name="com.example.android.codelabs.navigation.FlowStepFragment">

    <argument
        .../>

    <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">
    <!-- ...removed for simplicity-->
</fragment>

请注意:

  • 操作嵌套在目标内 - 这是您将从中导航的目标
  • 操作包含一个引用 flow_step_two_dest 的目标参数;这是您将导航到的目标的 ID
  • 此操作的 ID 是 "next_action"

以下是另一个示例,连接 flow_step_two_desthome_dest 的操作:

6d4a5b6f629c3910.png

<fragment
    android:id="@+id/home_dest"
    android:name="com.example.android.codelabs.navigation.HomeFragment"
    .../>

<fragment
    android:id="@+id/flow_step_two_dest"
    android:name="com.example.android.codelabs.navigation.FlowStepFragment">

    <argument
        .../>

    <action
        android:id="@+id/next_action"
        app:popUpTo="@id/home_dest">
    </action>
</fragment>

请注意:

  • 相同的 ID next_action 用于连接 flow_step_two_desthome_dest 的操作。您可以从 flow_step_one_destflow_step_two_dest 使用 next_action ID 进行导航。这是一个示例,说明操作如何提供抽象层,并可以根据上下文将您导航到不同的位置。
  • 使用了 popUpTo 属性 - 此操作将从返回堆栈中弹出 fragment,直到您到达 home_dest

现在是时候关联使用操作导航按钮,让它名副其实了!

  1. Design 模式打开 mobile_navigation.xml 文件
  2. home_dest 拖动一个箭头到 flow_step_one_dest

353eea1044f6d048.png

  1. 选中操作箭头(蓝色)后,更改操作的属性,使其成为:
  • ID = next_action
  • 进入过渡 = slide_in_right
  • 退出过渡 = slide_out_left
  • 弹出进入过渡 = slide_in_left
  • 弹出退出过渡 = slide_out_right

9670657fcc5a8de0.png

  1. 点击 Text 标签页

请注意 home_dest 目标下新添加的 next_action 操作。

mobile_navigation.xml

<fragment android:id="@+id/home_dest"
        ...>
        
        <action android:id="@+id/next_action"
            app:destination="@+id/flow_step_one"
            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. 打开 HomeFragment.kt
  2. navigate_action_button 添加点击监听器

HomeFragment.kt

 view.findViewById<Button>(R.id.navigate_action_button)?.setOnClickListener(
        Navigation.createNavigateOnClickListener(R.id.next_action, null)
)
  1. 验证轻触使用操作导航现在可以导航到下一个屏幕。

8. 使用 safe args 进行导航

Safe Args

Navigation Component 有一个 Gradle 插件,称为 safe args,它生成简单的对象和 builder 类,以便以类型安全的方式访问为目标和操作指定的参数。

使用 safe args 可以避免在目标之间传递值时使用这样的代码:

val username = arguments?.getString("usernameKey")

取而代之的是,使用具有生成 setter 和 getter 的代码。

val username = args.username

使用 safe args 传递值

  1. 打开项目 build.gradle 文件,注意 safe args 插件

build.gradle

dependencies {
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigationVersion"
    //...
    }

  1. 打开 app/build.gradle 文件,注意已应用的插件

app/build.gradle

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'androidx.navigation.safeargs.kotlin'

android { 
   //...
}
  1. 打开 mobile_navigation.xml,注意参数在 flow_step_one_dest 目标中的定义方式。

mobile_navigation.xml

<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...>
    </action>
</fragment>

使用 <argument> 标签,safeargs 会生成一个名为 FlowStepFragmentArgs 的类。

d0e308373c2656d9.png

由于 XML 包含一个名为 flowStepNumber 的参数,由 android:name="flowStepNumber" 指定,生成的类 FlowStepFragmentArgs 将包含一个带有 getter 和 setter 的变量 flowStepNumber

36d12ab963cb6ca6.png

  1. 打开 FlowStepFragment.kt
  2. 注释掉下方显示的这行代码:

FlowStepFragment.kt

// Comment out this line
// val flowStepNumber = arguments?.getInt("flowStepNumber")

这种旧式的代码不是类型安全的。最好使用 safe args。

  1. 更新 FlowStepFragment 以使用代码生成的类 FlowStepFragmentArgs。这将以类型安全的方式获取 FlowStepFragment 参数。

FlowStepFragment.kt

val safeArgs: FlowStepFragmentArgs by navArgs()
val flowStepNumber = safeArgs.flowStepNumber

Safe Args 方向类

您还可以使用 safe args 以类型安全的方式进行导航,无论是否添加参数。您可以通过使用生成的 Directions 类来实现。

d0e308373c2656d9.png

对于每个具有操作的独立目标,都会生成 Directions 类。Directions 类包含目标拥有的每个操作的方法。

例如,HomeFragment.kt 中的 navigate_action_button 点击监听器可以更改为:

HomeFragment.kt

// Note the usage of curly braces since we are defining the click listener lambda
view.findViewById<Button>(R.id.navigate_action_button)?.setOnClickListener {
    val flowStepNumberArg = 1
    val action = HomeFragmentDirections.nextAction(flowStepNumberArg)
    findNavController().navigate(action)
}

HomeFragmentDirections.nextAction(flowStepNumberArg)

9. 使用菜单、抽屉和底部导航进行导航

Navigation Components 包含一个名为 NavigationUI 的类以及 navigation-ui-ktx kotlin 扩展。NavigationUI 有静态方法,用于将菜单项与导航目标相关联,而 navigation-ui-ktx 是一组执行相同功能的扩展函数。如果 NavigationUI 在当前图中找到一个菜单项,其 ID 与某个目标的 ID 相同,它会配置该菜单项以导航到该目标。

将 NavigationUI 与选项菜单一起使用

使用 NavigationUI 最简单的方法之一是让它简化选项菜单的设置。特别是,NavigationUI 简化了对 onOptionsItemSelected 回调的处理。

  1. 打开 MainActivity.kt

注意您如何在 onCreateOptionsMenu 中已经有了膨胀菜单 overflow_menu 的代码。

  1. 打开 res/menu/overflow_menu.xml
  2. 更新您的溢出菜单以包含 settings_dest

overflow_menu.xml

<item
    android:id="@+id/settings_dest"
    android:icon="@drawable/ic_settings"
    android:menuCategory="secondary"
    android:title="@string/settings" />
  1. 打开 MainActivity.kt
  2. 让 NavigationUI 使用 onNavDestinationSelected 助手方法处理 onOptionsItemSelected。如果菜单项不是用于导航的,则使用 super.onOptionsItemSelected 处理。

MainActivity.kt

override fun onOptionsItemSelected(item: MenuItem): Boolean {
    return item.onNavDestinationSelected(findNavController(R.id.my_nav_host_fragment))
            || super.onOptionsItemSelected(item)
}
  1. 运行您的应用。您应该会看到一个功能正常的 ActionBar 菜单,可以导航到 SettingsFragment。

30a096187fb852fe.png

使用 NavigationUI 配置底部导航

代码中已包含实现底部导航的 XML 布局代码,这就是您看到底部导航栏的原因。但它还不能导航到任何地方。

  1. 打开 res/layout/navigation_activity/navigation_activity.xml (h470dp) 并点击 Text 标签页

注意底部导航的 XML 布局代码在那里,并引用了 bottom_nav_menu.xml

<com.google.android.material.bottomnavigation.BottomNavigationView
    android:id="@+id/bottom_nav_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:menu="@menu/bottom_nav_menu" />
  1. 打开 res/menu/bottom_nav_menu.xml

注意底部导航有两个项目,并且它们的 id 与导航图目标的 id 匹配

bottom_nav_menu.xml

<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@id/home_dest"
        android:icon="@drawable/ic_home"
        android:title="@string/home" />
    <item
        android:id="@id/deeplink_dest"
        android:icon="@drawable/ic_android"
        android:title="@string/deeplink" />
</menu>

让我们使用 NavigationUI 使底部导航真正发挥作用。

  1. 打开 MainActivity.kt
  2. 使用 setupWithNavController(bottomNavigationView: BottomNavigationView, navController: NavController) 实现 setupBottomNavMenu 方法

MainActivity.kt

private fun setupBottomNavMenu(navController: NavController) {
    val bottomNav = findViewById<BottomNavigationView>(R.id.bottom_nav_view)
    bottomNav?.setupWithNavController(navController)
}

现在您的底部导航正常工作了!

使用 NavigationUI 配置导航抽屉

最后,让我们使用 NavigationUI 配置侧边导航和导航抽屉,包括处理 ActionBar 和正确的向上导航。如果您有足够大的屏幕或屏幕太短无法容纳底部导航,您将看到此效果。

首先观察正确的布局 XML 代码是如何已在应用中。

  1. 打开 navigation_activity.xmlnavigation_activity.xml (w960dp)

注意两个布局都包含一个连接到 nav_drawer_menu 的 NavigationView。在平板电脑版本 (w960dp) 中,NavigationView 始终显示在屏幕上。在较小的设备上,NavigationView 被嵌套在 DrawerLayout 内

现在开始实现 NavigationView 导航。

  1. 打开 MainActivity.kt
  2. 使用 setupWithNavController(navigationView: NavigationView, navController: NavController) 实现 setupNavigationMenu 方法。注意此版本的 方法接受一个 NavigationView 而不是 BottomNavigationView

MainActivity.kt

private fun setupNavigationMenu(navController: NavController) {
    val sideNavView = findViewById<NavigationView>(R.id.nav_view)
    sideNavView?.setupWithNavController(navController)
}

现在导航视图菜单将显示在屏幕上,但它不会影响 ActionBar。

设置 ActionBar 需要创建一个 AppBarConfiguration 实例。AppBarConfiguration 的目的是指定您希望用于工具栏、可折叠工具栏和操作栏的配置选项。配置选项包括该栏是否必须处理抽屉布局以及哪些目标被视为顶级目标

顶级目标是您应用的根级别目标。这些目标在应用栏中不显示“向上”按钮,如果目标使用抽屉布局,则显示抽屉图标。

6cd3eaaf843ff226.png

  1. 通过传入一组顶级目标 ID 和抽屉布局来创建一个 AppBarConfiguration

MainActivity.kt

val drawerLayout : DrawerLayout? = findViewById(R.id.drawer_layout)
appBarConfiguration = AppBarConfiguration(
        setOf(R.id.home_dest, R.id.deeplink_dest),
        drawerLayout)

现在您有了 AppBarConfiguration,您可以调用 NavigationUI.setupActionBarWithNavController。这将执行以下操作:

  • 根据目标的标签在 ActionBar 中显示标题
  • 当您不在顶级目标时,显示“向上”按钮
  • 当您位于顶级目标时,显示抽屉图标(汉堡图标)
  1. 实现 setupActionBarWithNavController

MainActivity.kt

private fun setupActionBar(navController: NavController,
                           appBarConfig : AppBarConfiguration) {
    setupActionBarWithNavController(navController, appBarConfig)
}

您还应该让 NavigationUI 处理按下“向上”按钮时发生的事情。

  1. 覆盖 onSupportNavigationUp 并调用 NavigationUI.navigateUp,使用相同的 AppBarConfiguration

MainActivity.kt

override fun onSupportNavigateUp(): Boolean {
    return findNavController(R.id.my_nav_host_fragment).navigateUp(appBarConfiguration)
}
  1. 运行您的代码。如果您在分屏模式下打开应用,您应该会看到一个可以正常工作的导航抽屉。“向上”图标和抽屉图标应在适当的时候显示并正常工作。

d5474187c1c710cc.gif

将新目标添加到 NavigationView 很简单。一旦您有了可以正常工作的导航抽屉以及向上和返回导航,您只需要添加新的菜单项。

  1. 打开 menu/nav_drawer_menu.xml
  2. settings_dest 添加一个新的菜单项
<item
    android:id="@+id/settings_dest"
    android:icon="@drawable/ic_settings"
    android:title="@string/settings" />

现在您的导航抽屉将“设置”屏幕显示为一个目标。做得好!

b9c243170688706.png

10. 深度链接到目标

Navigation Components 还包含深度链接支持。深度链接是一种跳转到应用导航中间的方法,无论是从实际的 URL 链接还是来自通知的 PendingIntent。

使用导航库处理深度链接的一个好处是,它确保用户从其他入口点(如应用微件、通知或网页链接(将在下一步介绍))以正确的返回堆栈进入正确的目标。

导航提供了一个 NavDeepLinkBuilder 类,用于构造一个 PendingIntent,该 PendingIntent 将用户带到特定目标。

我们将使用 NavDeepLinkBuilder 将一个应用微件关联到目标。

  1. 打开 DeepLinkAppWidgetProvider.kt
  2. 添加一个使用 NavDeepLinkBuilder 构造的 PendingIntent

DeepLinkAppWidgetProvider

val args = Bundle()
args.putString("myarg", "From Widget");
val pendingIntent = NavDeepLinkBuilder(context)
        .setGraph(R.navigation.mobile_navigation)
        .setDestination(R.id.deeplink_dest)
        .setArguments(args)
        .createPendingIntent()

remoteViews.setOnClickPendingIntent(R.id.deep_link_button, pendingIntent)

请注意:

  • setGraph 包含导航图。
  • setDestination 指定链接指向何处。
  • setArguments 包含您希望传递给深度链接的任何参数。
  1. 将深度链接微件添加到您的主屏幕。轻触并按住主屏幕以查看添加微件的选项。

轻触并按住

向下滚动查找微件

完成后,您将拥有一个深度链接微件。

395d37cfa7f5421f.png

  1. 轻触微件,验证 Android 目标是否使用正确的参数打开。顶部应显示“From Widget”,因为这是您在 DeepLinkAppWidgetProvider 中传入的参数。

17266d930c15160f.png

  1. 验证按下返回按钮是否将您带到 home_dest 目标。

深度链接的返回堆栈是使用您传入的导航图确定的。如果您选择的显式 Activity 具有父 Activity,则这些父 Activity 也包含在内。

返回堆栈是使用由 app:startDestination 指定的目标生成的。在此应用中,我们只有一个 activity 和一个导航层级,因此返回堆栈将带您到 home_dest 目标。

更复杂的导航可以包含嵌套导航图。嵌套图中每个层级的 app:startDestination 决定了返回堆栈。有关深度链接和嵌套图的更多信息,请查阅导航原则

11. 将网页链接与目标关联

深度链接最常见的用途之一是允许网页链接在您的应用中打开一个 activity。传统上,您会使用一个 intent-filter 并将 URL 与您想要打开的 activity 关联起来

导航库使这变得非常简单,并允许您将 URL 直接映射到导航图中的目标。

<deepLink> 是您可以添加到图中目标的一个元素。每个 <deepLink> 元素都有一个必需的属性:app:uri

除了直接的 URI 匹配之外,还支持以下功能:

  • 没有方案的 URI 被假定为 http 和 https。例如,www.example.com 将匹配 http://www.example.comhttps://www.example.com
  • 您可以使用 {placeholder_name} 形式的占位符来匹配一个或多个字符。占位符的字符串值可在参数 Bundle 中获取,该 Bundle 的键名与占位符相同。例如,http://www.example.com/users/{id} 将匹配 http://www.example.com/users/4
  • 您可以使用 .* 通配符来匹配零个或多个字符。
  • NavController 将自动处理 ACTION_VIEW Intent 并查找匹配的深度链接。

在本步骤中,您将向 www.example.com 添加一个深度链接。

  1. 打开 mobile_navigation.xml
  2. deeplink_dest 目标添加一个 <deepLink> 元素。

mobile_navigation.xml

<fragment
    android:id="@+id/deeplink_dest"
    android:name="com.example.android.codelabs.navigation.DeepLinkFragment"
    android:label="@string/deeplink"
    tools:layout="@layout/deeplink_fragment">

    <argument
        android:name="myarg"
        android:defaultValue="Android!"/>

    <deepLink app:uri="www.example.com/{myarg}" />
</fragment>
  1. 打开 AndroidManifest.xml
  2. 添加 nav-graph 标签。这将确保生成适当的 intent 过滤器。

AndroidManifest.xml

<activity android:name=".MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>

    <nav-graph android:value="@navigation/mobile_navigation" />
</activity>
  1. 使用深度链接启动您的应用。有两种方法可以做到:
  • 使用 adb
adb shell am start -a android.intent.action.VIEW -d "http://www.example.com/urlTest" 
  • 通过 Google 应用导航。** 您应该可以在搜索栏中输入 www.example.com/urlTest,然后会出现一个消歧窗口。选择 Navigation codelab

使用搜索栏打开(不在 Chrome 中)

消歧对话框

无论哪种方式,您都应该在屏幕上看到消息“urlTest”。这是从 URL 传递到 fragment 的。

4d22e0ddbda4d8cf.png

12. [可选] 尝试自己导航

此 Codelab 应用中还有一个部分供您尝试,那就是购物车按钮。

dabd03fdaaee4d91.png

这是对此 Codelab 中您所学技能的回顾。此步骤不包含注释,请自行尝试。

  1. 创建一个新的 fragment 类
  2. 将 fragment 添加到您的导航图作为目标
  3. 让购物车图标打开您新的 fragment 类,使用 NavigationUI 处理菜单。

13. 恭喜!

您已熟悉 Navigation Component 背后的基本概念!在此 Codelab 中,您学习了:

  • 导航图结构
  • NavHostFragment 和 NavController
  • 如何导航到特定目标
  • 如何按操作导航
  • 如何在目标之间传递参数,包括使用新的 safeargs 插件
  • 使用菜单、底部导航和导航抽屉进行导航
  • 通过深度链接导航

您可以继续探索此应用,或开始在您自己的应用中使用导航。

还有更多值得尝试的功能,包括:

  • 从返回堆栈弹出目标(或任何返回堆栈操作)
  • 嵌套导航图
  • 条件导航
  • 添加对新目标的支持

有关 Navigation Component 的更多信息,请查阅文档Jetpack Compose Navigation Codelab。如果您有兴趣了解其他架构组件,请尝试以下 Codelab: