Navigation官方译文

概念

导航是应用程序设计的重要组成部分。通过导航,您可以设计允许用户在应用内的不同内容区域移动,进出和退出的交互。

导航架构组件

导航架构组件可帮助您在应用程序中实现常见但复杂的导航要求,使您能够更轻松地为用户提供一致且可预测的体验。

导航可处理在应用程序的目的地之间导航- 也就是说,在应用程序中用户可以导航到任何位置。虽然目标通常通过Fragments代表特定的屏幕,但导航架构组件支持下面列出的其他目标类型:

  • Activities
  • 导航图和子图 - 当目标是图形或子图时,用户导航到该图或子图的起始目标
  • 自订目标类型

这些目的地通过操作连接,一系列的目的地和连接操作组成应用程序的导航图。

导航架构组件提供了许多其他好处,包括:

  • 处理Fragment事务
  • 默认情况下正确处理向上和向后操作
  • 为动画和过渡提供标准化资源
  • 将深层链接视为一级操作
  • 包括导航UI模式,例如导航抽屉和底部导航,只需最少的额外工作
  • 在导航传递参数时提供类型安全性
  • 使用Android Studio的导航编辑器可视化和编辑导航图

⚠️:如果要将导航架构组件与Android Studio一起使用,则必须使用Android Studio 3.2 Canary 14或更高版本。

导航原理

导航架构组件基于以下设计原则:

固定起始目的地

应用应具有固定的起始目的地,即用户从启动器启动应用时看到的屏幕,以及用户在按下后退按钮返回启动器之前看到的最后一个屏幕。

⚠️:应用可能具有一次性设置或一系列登录屏幕。这些条件屏幕不应被视为您应用的起始目的地。

导航状态应该通过堆栈目的地来表示

一个导航堆栈应该有应用程序的起始目标在堆栈的底部,并在堆栈顶部的当前目标。

更改导航堆栈的操作应始终在导航堆栈的顶部操作,方法是将新目标推送到堆栈顶部或从堆栈顶部弹出最顶层目标。

“向上”按钮永远不会退出您的应用

如果用户位于起始目的地,则不应显示“向上”按钮。当您的应用程序使用其他应用程序任务的深层链接启动时,Up应该将用户带到分层父目标,而不是返回到其他应用程序。

在应用程序的任务中,向上和向后相同

当系统“后退”按钮不会退出您的应用程序时,例如当您执行自己的任务而不是启动目标时,“向上”按钮的功能应与系统“后退”按钮完全相同。

深度链接和导航到目标应该产生相同的堆栈

用户应该能够使用“后退”或“上移”按钮,无论他们如何到达目的地,都可以通过目的地返回到起始目的地。
深度链接时,将删除任何现有的导航堆栈,并替换为深层链接的导航堆栈。

使用

通过导航组件实现导航,Jetpack的架构组件通过提供一组处理大部分细节的导航组件,可以轻松实现应用内导航。

使用Navigation,您可以创建导航图,这是一种XML资源,表示应用程序中的各个目标节点以及连接节点的操作。

下图显示了一个示例应用程序的导航图的直观表示,该应用程序包含通过5个操作连接的6个目标。
导航图

一个目标就是你可以在你的应用程序导航到任何地方。虽然目标通常是表示特定屏幕的Fragments,但导航支持其他目标类型:

  • Activities
  • 导航图和子图-当目标是图形或子图时,导航到该图或子图的起始目标
  • 自订目标类型
    ⚠️:导航组件专为具有一个具有多个Fragment目标的主要activity的应用程序而设计。主activity托管导航图,并负责根据需要交换目标。在具有多个activity目标的应用中,每个附加activity都会托管自己的导航图。有关更多信息,请参阅修改活动以主持导航 。

为项目引入导航组件

⚠️:如果您要使用Android Studio导航,则必须使用 Android Studio 3.2 Canary 14或更高版本。

要在项目中包含导航支持,请将以下内容添加到应用程序的 build.gradle文件中。

1
2
3
4
5
6
dependencies {
def nav_version = "1.0.0-alpha09"

implementation "android.arch.navigation:navigation-fragment:$nav_version" // use -ktx for Kotlin
implementation "android.arch.navigation:navigation-ui:$nav_version" // use -ktx for Kotlin
}

有关向项目添加其他体系结构组件的信息,请参阅向项目 添加组件。

创建导航图

要向项目添加导航图,请执行以下操作:

  1. 在“项目”窗口中,右键单击该res目录,然后选择 New > Android Resource File。出现 New Resource File 对话框。
  2. 在 File name 字段中键入名称,例如“nav_graph”。
  3. 从 Resource type 下拉列表中选择 Navigation。
  4. 单击 OK 。发生以下情况:

    • 在navigation目录中创建资源res目录。
    • nav_graph.xml在导航目录中创建一个文件。
    • 该 nav_graph.xml文件将在导航编辑器中打开。此XML文件包含导航图。
  5. 单击 Text 选项卡以切换到XML文本视图。您应该看到一个空的导航图,如以下示例所示:

1
2
3
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android">
</navigation>
  1. 单击 Design 返回 Navigation Editor。

浏览导航编辑器

在Navigation Editor中,您可以直观地编辑导航图,而不是直接编辑基础XML。
导航编辑器

  • 目的地:列出导航主机和当前位于“ 曲线图编辑器”中的所有目的地。
  • 图表编辑器:包含导航图的可视化表示。
  • 属性:显示导航图中当前所选项的属性。

创建目的地

创建导航图的第一步是识别和创建应用的各个目标。您可以在现有项目中的Fragments和Activities中创建目标,也可以创建占位符目标,以后可以使用Fragments或Activities替换它们。

如果您要将现有片段或活动添加到导航图中,或者如果要添加占位符,请单击 New Destination ➕,然后在下拉列表中单击相应的片段,活动或占位符目标。出现。现在,您可以在 Design 视图中查看目标的预览以及导航图的“ 文本”视图中的相应XML 。

要创建新的目标类型,请执行以下操作:

  1. 在导航编辑器中,单击 New Destination ➕,然后单击 Create blank destination 。
  2. 在出现的 New Android Component 对话框中,输入 Fragment Name 。这是Fragment类的名称。
  3. 要让Android Studio为Fragment创建相应的布局资源文件,请选中 Create layout XML 旁边的框,然后在 Fragment Layout Name 字段中输入资源名称。
  4. 在 Source Language 下拉列表中,为类源文件选择Kotlin或Java。
  5. 单击完成。
    新目标类型显示在导航编辑器中。Android Studio还以指定的源语言创建相应的类文件,如果指定,还会为新目标类型创建布局资源文件。

图显示了目标和占位符的示例。
目标和占位符

可以单击任何目的地以选择它。选择目标时,Attributes 面板中将显示以下属性:

  • Type 字段指示目标是否在源代码中实现为片段或活动。
  • Label 字段包含目标的XML布局文件的名称。
  • ID 字段包含被用于指在代码中的目的地的目的地的ID。
  • Class 下拉列表显示与目标关联的类的名称。您可以单击此下拉列表将关联的类更改为其他目标类型。

⚠️:占位符与类无关。请务必在运行应用程序之前更改类值。

单击 Text 选项卡以显示导航图的XML视图。XML包含相同的id,name,label,和layout属性的目的地,如下所示:

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
app:startDestination="@id/blankFragment">
<fragment
android:id="@+id/blankFragment"
android:name="com.example.cashdog.cashdog.BlankFragment"
android:label="Blank"
tools:layout="@layout/fragment_blank" />
</navigation>

连接目的地

您必须有多个目标才能连接目标。以下是包含两个空白目标的导航图的XML:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
app:startDestination="@id/blankFragment">
<fragment
android:id="@+id/blankFragment"
android:name="com.example.cashdog.cashdog.BlankFragment"
android:label="fragment_blank"
tools:layout="@layout/fragment_blank" />
<fragment
android:id="@+id/blankFragment2"
android:name="com.example.cashdog.cashdog.BlankFragment2"
android:label="Blank2"
tools:layout="@layout/fragment_blank_fragment2" />
</navigation>

目的地使用动作连接。要连接两个目标,请执行以下操作:

  1. 在 Design 选项卡中,将鼠标悬停在您希望用户导航的目标的右侧。目的地上会出现一个圆圈。
    视图设计
    图4.动作连接圈
  2. 单击并按住,将光标拖到您希望用户导航到的目标上,然后释放。绘制一条线以指示两个目的地之间的导航。
    连接目的地
    图5.连接目的地
    单击箭头以突出显示该操作。Attributes 面板中显示以下属性:

    • Type 字段包含 Action。
    • 该ID字段包含动作ID。
    • Destination 字段包含所述目的地片Fragment或Activity的ID。
      单击 Text 选项卡以切换到XML视图。现在,操作元素已添加到源目标。该操作具有ID和目标属性,其中包含下一个目标的ID,如下例所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
app:startDestination="@id/blankFragment">
<fragment
android:id="@+id/blankFragment"
android:name="com.example.cashdog.cashdog.BlankFragment"
android:label="fragment_blank"
tools:layout="@layout/fragment_blank" >
<action
android:id="@+id/action_blankFragment_to_blankFragment2"
app:destination="@id/blankFragment2" />
</fragment>
<fragment
android:id="@+id/blankFragment2"
android:name="com.example.cashdog.cashdog.BlankFragment2"
android:label="fragment_blank_fragment2"
tools:layout="@layout/fragment_blank_fragment2" />
</navigation>

将屏幕指定为起始目的地

导航编辑器使用🏠来指示用户在打开应用程序时看到的第一个屏幕,也称为 起始目的地。

要选择其他起始目的地,请执行以下操作:

  1. 在 Design 选项卡中,单击新的起始目标以突出显示它。

  2. 单击 Assign start destination 按钮🏠。或者,您可以右键单击目标,然后单击 Set as Start Destination。

修改活动以主持导航

Activity为NavHost中的应用程序提供导航。 NavHost是一个空容器,当用户浏览您的应用程序时,目的地会被换入和换出。
Navigation组件中默认NavHost的实现是NavHostFragment。

使用布局编辑器添加NavHostFragment

您可以使用布局编辑器通过以下步骤将NavHostFragment添加到Activity:

  1. 如果您还没有导航图资源,请创建导航图资源。
  2. 在项目文件列表中,双击活动的布局XML文件,在布局编辑器中将其打开。
  3. 在Palette窗格中,选择Containers类别,或者搜索“NavHostFragment”。
  4. 将NavHostFragment视图拖到您的活动上。
  5. 接下来,在出现的 Navigation Graphs 对话框中,选择要与其关联的相应导航图NavHostFragment,然后单击 OK 。

回到 Text 视图,请注意Android Studio添加了类似于以下内容的XML:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"

app:defaultNavHost="true"
app:navGraph="@navigation/mobile_navigation" />

</android.support.constraint.ConstraintLayout>

该app:defaultNavHost=”true”属性确保您NavHostFragment 拦截系统后退按钮。您还可以通过覆盖AppCompatActivity.onSupportNavigateUp() 和调用来实现此行为NavController.navigateUp,如下例所示:

1
2
3
4
5
/*  override fun onSupportNavigateUp(): Boolean {
return Navigation.findNavController(this, R.id.fragment_nav).navigateUp()
}*/

override fun onSupportNavigateUp() = Navigation.findNavController(this, R.id.fragment_nav).navigateUp()

以编程方式创建NavHostFragment

您还可以使用NavHostFragment.create() 以编程方式创建NavHostFragment具有特定图形资源的资源,如以下示例所示:

1
2
3
4
5
val finalHost = NavHostFragment.create(R.navigation.example_graph)
supportFragmentManager.beginTransaction()
.replace(R.id.nav_host, finalHost)
.setPrimaryNavigationFragment(finalHost) // this is the equivalent to app:defaultNavHost="true"
.commit()

将目标绑定到ui小部件

使用NavController类导航到目标。 可以使用以下静态方法之一检索NavController::

  • NavHostFragment.findNavController(Fragment)
  • Navigation.findNavController(Activity, @IdRes int viewId)
  • Navigation.findNavController(View)
    检索后 NavController,使用其 navigate() 方法导航到目标。该 navigate() 方法接受资源ID。ID可以是导航图或操作中特定目标的ID。使用操作的ID而不是目标的资源ID具有优势,例如将过渡与导航相关联。有关转换的更多信息,请参阅创建目标之间的转换。

以下代码段显示了如何导航到 ViewTransactionsFragment:

1
2
3
viewTransactionsButton.setOnClickListener { view ->
view.findNavController().navigate(R.id.viewTransactionsAction)
}

Android系统维护一个包含最后访问目的地的 back stack 。当用户打开应用程序时,应用程序的第一个目标位于堆栈中。每次调用该 navigate() 方法都会将另一个目标放在堆栈顶部。相反,按“向上”或“返回”按钮分别调用 NavController.navigateUp() 和 NavController.popBackStack() 方法,将顶部目标弹出堆栈。

对于按钮,您还可以使用 Navigation类的 createNavigateOnClickListener() 便捷方法导航到目标:

1
button.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.next_fragment, null))

要处理其他常见UI组件,例如顶部应用栏和底部导航,请参阅使用NavigationUI更新UI组件。

为目标创建深层链接

在Android中,深层链接是指向应用中特定目标的URI。当您想要将用户发送到特定目的地以在您的应用中执行某项任务时,这些URI很有用,例如发送资金流,允许用户快速汇款给某人。

为目标分配深层链接

要在导航图中为目标指定深层链接,请执行以下操作:

  • 在 Design 视图中,选择深层链接的目标。
  • 单击 Add deep link 按钮🔗(或单击 Attributes 面板的 Deep Links 部分中的➕)。出现 Add Deep Link 对话框。
  • 在URI字段中键入URI,例如“www.cashdog.com/sendmoney”,它表示应用中发送货币嵌套图的起始目的地。

请注意以下事项:

  • 未指定协议的URI被假定为http或https。例如, www.cashdog.com匹配http://www.cashdog.com 或https://www.cashdog.com。
  • 占位符的{placeholder_name}匹配形式为1个或多个字符。String占位符的值在Bundle带有相同名称的键的参数中可用。例如,http://www.example.com/users/{id}匹配http://www.example.com/users/4.
  • 。*通配符可用于匹配0个或多个字符。
    • (可选)选中 Auto Verify 以要求Google验证您是URI的所有者。有关更多信息,请参阅验证Android应用程序链接。
    • 单击 Add。 所选目标上方会显示一个链接图标,表示该目标具有深层链接。
    • 单击 Text 选项卡以切换到XML视图。已将嵌套的深层链接元素添加到目标:
      1
      <deepLink app:uri="https://cashdog.com/sendmoney"/>

当用户从深层链接目标按下“后退”按钮时,他们会导航回导航堆栈,就像他们从应用程序的入口点进入您的应用程序一样。

为深层链接添加意图过滤器

您必须添加manifest.xml文件以在您的应用中启用深层链接:

对于Android Studio 3.0和3.1,您必须手动添加intent-filter 元素。有关更多信息,请参阅 创建应用程序内容的深层链接。
对于Android Studio 3.2+,您可以nav-graph向活动元素添加元素:

1
2
3
<activity name=".MainActivity">
<nav-graph android:value="@navigation/main_nav" />
</activity>

作为清单合并构建步骤的一部分,此元素将替换为匹配导航图中所有深层链接所需的生成元素。

以编程方式使用NavDeepLinkBuilder创建深层链接

您可以使用NavDeepLinkBuilder该类构建一个PendingIntent 将用户带到特定目标的类。

触发此深层链接时,将清除任务后台堆栈并替换为深层链接目标。当嵌套图表,从每一级的开始目的地嵌套的,也就是说,从每个目的地开始 在元件层次结构也被添加到堆栈中。

您可以PendingIntent直接 构造一个NavDeepLinkBuilder(Context),如下面的示例所示。请注意,如果提供的上下文不是a Activity,则构造函数将使用 PackageManager.getLaunchIntentForPackage() 作为默认活动来启动(如果可用)。

1
2
3
4
5
val pendingIntent = NavDeepLinkBuilder(context)
.setGraph(R.navigation.mobile_navigation)
.setDestination(R.id.android)
.setArguments(args)
.createPendingIntent()

有关包含此示例的完整导航项目,请参阅 Navigation Codelab。

⚠️:如果您有现有的NavController,您还可以通过创建深层链接NavController.createDeepLink()。

在目的地之间创建过渡

导航组件提供了在目的地之间轻松添加转换(例如淡入和淡出)的功能。要添加转换,请执行以下操作:

  1. 创建动画资源文件。导航支持属性和视图动画。有关详细信息,请参阅 Animation resources。
  2. 在导航编辑器的 https://developer.android.com/guide/topics/resources/animation-resource 选项卡中,单击要进行转换的操作。
  3. 在 Attributes 面板的 Transitions 部分中,单击 Enter 旁边的向下箭头以显示项目中可用过渡的列表。
  4. 选择用户进入目的地时发生的转换。
  5. 回到 Transitions 部分,单击 Exit 旁边的向下箭头,然后选择当用户退出目标时发生的转换。
  6. 单击 Text 选项卡以切换到XML文本视图。转换的XML出现在指定操作的元素中。该操作嵌入在转换发生之前处于活动状态的目标的XML中。在以下示例中, specifyAmountFragment是活动目标,因此它包含具有过渡动画的操作:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <fragment
    android:id="@+id/specifyAmountFragment"
    android:name="com.example.buybuddy.buybuddy.SpecifyAmountFragment"
    android:label="fragment_specify_amount"
    tools:layout="@layout/fragment_specify_amount">
    <action
    android:id="@+id/confirmationAction"
    app:destination="@id/confirmationFragment"
    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>

在此示例中,我们在移动到目的地时(enterAnim以及exitAnim退出该目标(popEnterAnim 和popExitAnim)时发生转换)。

在Fragment目标之间添加共享元素转换

除了过渡动画之外,Navigation还支持在目标之间添加共享元素过渡。

与动画不同,共享元素转换是以编程方式提供的,而不是通过导航XML文件提供的,因为它们需要引用View您希望包含在共享元素转换中的 实例。

每种类型的目标都通过Navigator.Extras 接口的子类实现此编程API 。将Extras被传递到一个呼叫navigate()。

Fragment目标共享元素转换
在FragmentNavigator.Extras 类允许您附加共享元素到navigate()呼叫目的地的片段,如图下面的例子:

1
2
3
4
5
6
7
val extras = FragmentNavigatorExtras(
imageView to "header_image",
titleView to "header_title")
view.findNavController().navigate(R.id.confirmationAction,
null, // Bundle of args
null, // NavOptions
extras)

⚠️:这里的header_image和header_title要确保在当前布局的android:transitionName和需要调整的布局的android:transitionName一样

Activity目标共享元素转换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// Rename the Pair class from the Android framework to avoid a name clash
import android.util.Pair as UtilPair
///...
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(activity,
UtilPair.create(imageView, "header_image"),
UtilPair.create(titleView, "header_title"))
val extras = ActivityNavigator.Extras(options)
view.findNavController().navigate(R.id.details,
null, // Bundle of args
null, // NavOptions
extras)

```



### 使用NavigationUI更新UI组件

导航架构组件包括NavigationUI类。 此类包含使用顶部应用栏,导航抽屉和底部导航管理导航的静态方法。

#### 监听导航事件
与NavController交互是在目的地之间导航的主要方法。 NavController负责用新目标替换NavHost的内容。 在许多情况下,UI元素 - 例如顶级应用栏或其他持久性导航控件(如BottomNavigationBar)在NavHost之外存在,并且需要在目的地之间导航时进行更新。

NavController提供了一个OnDestinationChangedListener接口,当NavController的当前目标或其参数发生更改时,该接口将被调用。 可以通过addOnDestinationChangedListener()方法注册新的侦听器,如下面的示例所示。 请注意,在调用addOnDestinationChangedListener()时,如果当前目标存在,则会立即将其发送给您的侦听器。

```Kotlin
navController.addOnDestinationChangedListener { navController, destination, arguments ->
textView.setText(destination.label)
}

NavigationUI使用OnDestinationChangedListener使这些常见的UI组件可以感知导航事件。 但请注意,您还可以单独使用OnDestinationChangedListener来使任何自定义UI或业务逻辑感知导航事件。

顶部应用栏

顶部应用栏在应用顶部 提供了一致的位置,用于显示当前屏幕的信息和操作。
顶部导航
NavigationUI包含在用户浏览应用时自动更新顶部应用栏中内容的方法。例如,NavigationUI使用导航图中的目标标签可以使顶部应用栏的标题保持最新。

使用NavigationUI下面讨论的顶级应用栏方法时,可以使用{argName}标签中的格式从提供给目标的参数中自动填充附加到目标的标签。

NavigationUI 提供以下顶级应用栏类型的支持:

  • Toolbar
  • CollapsingToolbarLayout
  • ActionBar

AppBarConfiguration

NavigationUI使用AppBarConfiguration 对象来管理应用程序显示区域左上角的“导航”按钮的行为。默认情况下,当用户位于导航图的顶级目标位置时,导航按钮将隐藏,并在任何其他目标位置显示为“向上”按钮。

要将导航图的起始目的地用作唯一的顶级目的地,您可以创建一个AppBarConfiguration对象并传入相应的导航图,如下所示:

1
val appBarConfiguration = AppBarConfiguration(navController.graph)

如果要自定义哪些目标被视为顶级目标,则可以将一组目标ID传递给构造函数,如下所示:

1
val appBarConfiguration = AppBarConfiguration(setOf(R.id.main, R.id.android))

创建Toolbar

要使用NavigationUI创建工具栏,请首先在Activity中定义工具栏,如下所示:

1
2
3
4
5
6
7
8
<LinearLayout>
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar" />
<fragment
android:id="@+id/nav_host_fragment"
... />
...
</LinearLayout>

接下来,setupWithNavController() 从您的主要活动的onCreate()方法调用,如下所示:

1
2
3
4
5
6
7
8
override fun onCreate(savedInstanceState: Bundle?) {
setContentView(R.layout.activity_main)

...
//主导航Fragment
val navHostFragment = nav_host_fragment as NavHostFragment
// 其中toolbal为需要绑定的视图控件,目前支持12种视图控件;
NavigationUI.setupWithNavController(toolbar, navHostFragment.navController)

⚠️:注意:使用工具栏时,导航会自动处理“导航”按钮的单击事件,因此您无需覆盖onSupportNavigateUp()。!!!如果使用带menu菜单的控件如BottomNavigationView时,需要确保menu的id和需要跳转的导航图中的fragment的ID一致!!!

包括CollapsingToolbarLayout

要CollapsingToolbarLayout在工具栏中添加a ,请先在主要活动中定义工具栏和周围布局,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<LinearLayout>
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="@dimen/tall_toolbar_height">

<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/collapsing_toolbar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:contentScrim="?attr/colorPrimary"
app:expandedTitleGravity="top"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">

<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>

<fragment
android:id="@+id/nav_host_fragment"
... />
...
</LinearLayout>

接下来,setupWithNavController() 从您的主要活动的onCreate方法调用,如下所示:

1
2
3
4
5
6
7
8
9
override fun onCreate(savedInstanceState: Bundle?) {
setContentView(R.layout.activity_main)

...

//主导航Fragment
val navHostFragment = nav_host_fragment as NavHostFragment
// 其中toolbal为需要绑定的视图控件,目前支持12种视图控件;
NavigationUI.setupWithNavController(toolbar, navHostFragment.navController)

Action bar

要使用默认操作栏包含导航支持,请setupActionBarWithNavController() 从主Activity的onCreate()方法中调用 ,如下所示。请注意,您需要声明自己的AppBarConfiguration外部onCreate(),因为您在覆盖时也使用它onSupportNavigateUp():

1
2
3
4
5
6
7
8
9
10
11
private lateinit var appBarConfiguration: AppBarConfiguration

...

override fun onCreate(savedInstanceState: Bundle?) {
...

val navController = findNavController(R.id.nav_host_fragment)
appBarConfiguration = AppBarConfiguration(navController.graph)
setupActionBarWithNavController(navController, appBarConfiguration)
}

接下来,覆盖onSupportNavigateUp()以处理向上导航:

1
2
3
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}

将目的地绑定到菜单项

NavigationUI还提供了将目标绑定到菜单驱动的UI组件的帮助程序。NavigationUI包含一个辅助方法,onNavDestinationSelected()它MenuItem与NavController托管关联目标的方法一起使用 。如果id在的MenuItem比赛的id目标时,NavController可以然后导航到目的地。

作为一个例子,下面的XML片断定义一个菜单项,并具 有共同的目的地id(id需要一致) ,details_page_fragment:

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
... >

...

<fragment android:id="@+id/details_page_fragment"
android:label="@string/details"
android:name="com.example.android.myapp.DetailsFragment" />
</navigation>

1
2
3
4
5
6
7
8
9
<menu xmlns:android="http://schemas.android.com/apk/res/android">

...

<item
android:id="@id/details_page_fragment"
android:icon="@drawable/ic_details"
android:title="@string/details" />
</menu>

例如,如果您的菜单是通过Activity的onCreateOptionsMenu()添加的,则可以通过覆盖Activity的onOptionsItemSelected()来调用onNavDestinationSelected()来关联菜单项和目标,如下所示:

1
2
3
4
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val navController = findNavController(R.id.nav_host)
return item.onNavDestinationSelected(navController) || super.onOptionsItemSelected(item)
}

现在,当用户单击details_page_fragment菜单项时,应用程序会自动导航到 具有相同ID的相应目标 。

添加导航抽屉

导航抽屉是一个UI面板,显示应用程序的主导航菜单。 当用户触摸应用栏中的抽屉图标或用户从屏幕的左边缘滑动手指时,抽屉出现。
导航抽屉
抽屉图标显示在使用DrawerLayout的所有顶级目标上。 顶级目标是应用程序的根级目标。 它们不会在应用栏中显示“向上”按钮。

要添加导航抽屉,首先将DrawerLayout声明为根视图。 在DrawerLayout内,添加主UI内容的布局和包含导航抽屉内容的另一个视图。

例如,以下布局使用具有两个子视图的DrawerLayout:NavHostFragment包含主要内容,NavigationView用于导航抽屉的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?xml version="1.0" encoding="utf-8"?>
<!-- Use DrawerLayout as root container for activity -->
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">

<!-- Layout to contain contents of main body of screen (drawer will slide over this) -->
<fragment
android:name="androidx.navigation.fragment.NavHostFragment"
android:id="@+id/nav_host_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />

<!-- Container for contents of drawer - use NavigationView to make configuration easier -->
<android.support.design.widget.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true" />

</android.support.v4.widget.DrawerLayout>

接下来,DrawerLayout 通过将其传递到导航图,将其连接到AppBarConfiguration,如下所示:

1
val appBarConfiguration = AppBarConfiguration(navController.graph, drawerLayout)

⚠️:使用时NavigationUI,顶部应用栏帮助器会在当前目标更改时自动在抽屉图标和“向上”图标之间切换。你不需要使用 ActionBarDrawerToggle。
接下来,在主Activity类中,setupWithNavController() 从主活动的onCreate()方法调用 ,如下所示:

1
2
3
4
5
6
7
8
9
override fun onCreate(savedInstanceState: Bundle?) {
setContentView(R.layout.activity_main)

...

val navController = findNavController(R.id.nav_host_fragment)
findViewById<NavigationView>(R.id.nav_view)
.setupWithNavController(navController)
}

底部导航

NavigationUI也可以处理底部导航。当用户选择菜单项时,NavController调用 onNavDestinationSelected() 并自动更新底部导航栏中的所选项。
底部导航
要在应用中创建底部导航栏,请先在主要活动中定义栏,如下所示:

1
2
3
4
5
6
7
8
9
<LinearLayout>
...
<fragment
android:id="@+id/nav_host_fragment"
... />
<android.support.design.widget.BottomNavigationView
android:id="@+id/bottom_nav"
app:menu="@menu/menu_bottom_nav" />
</LinearLayout>

接下来,在Activity中,setupWithNavController() 从主活动的onCreate()方法调用 ,如下所示:

1
2
3
4
5
6
7
8
9
override fun onCreate(savedInstanceState: Bundle?) {
setContentView(R.layout.activity_main)

...

val navController = findNavController(R.id.nav_host_fragment)
findViewById<BottomNavigationView>(R.id.bottom_nav)
.setupWithNavController(navController)
}

嵌套导航图

可以将一系列目标分组为称为根图的父导航图中的嵌套图。 嵌套图对于组织和重用应用程序UI的各个部分非常有用,例如自包含的登录流程。

嵌套图封装了其目标。 与根图一样,嵌套图必须将目标标识为起始目标。 嵌套图外部的目标(例如根图上的目标)仅通过其起始目标访问嵌套图。

图显示了一个简单的汇款应用程序的导航图。 从左侧的起始目的地开始,图表有两个流程:一个沿着顶部用于发送货币,另一个沿着底部用于查看帐户余额。
汇款导航图
要将目标分组到嵌套图中,请执行以下操作:

  1. 在导航编辑器中,按住Shift键,然后单击要包含在嵌套图中的目标。
  2. 右键单击以打开上下文菜单,然后选择 Move to Nested Graph > New Graph 。目标包含在嵌套图中。图显示了 Navigation Editor 中的嵌套图:
    Graph Editor中的嵌套图
  3. 单击嵌套图。Attributes 面板中显示以下属性 :

    • Type,其中包含”Nested Graph”
    • ID,包含嵌套图的系统分配ID。此ID用于引用代码中的嵌套图。
  4. 双击嵌套图形以显示其目标。

  5. 单击 Text 选项卡以切换到XML视图。图表中添加了嵌套导航图。此导航图有自己的navigation 元素,以及它自己的ID和startDestination指向嵌套图中第一个目标的属性:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    <?xml version="1.0" encoding="utf-8"?>
    <navigation xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    app:startDestination="@id/mainFragment">
    <fragment
    android:id="@+id/mainFragment"
    android:name="com.example.cashdog.cashdog.MainFragment"
    android:label="fragment_main"
    tools:layout="@layout/fragment_main" >
    <action
    android:id="@+id/action_mainFragment_to_chooseRecipient"
    app:destination="@id/sendMoneyGraph" />
    <action
    android:id="@+id/action_mainFragment_to_viewBalanceFragment"
    app:destination="@id/viewBalanceFragment" />
    </fragment>
    <fragment
    android:id="@+id/viewBalanceFragment"
    android:name="com.example.cashdog.cashdog.ViewBalanceFragment"
    android:label="fragment_view_balance"
    tools:layout="@layout/fragment_view_balance" />
    <navigation android:id="@+id/sendMoneyGraph" app:startDestination="@id/chooseRecipient">
    <fragment
    android:id="@+id/chooseRecipient"
    android:name="com.example.cashdog.cashdog.ChooseRecipient"
    android:label="fragment_choose_recipient"
    tools:layout="@layout/fragment_choose_recipient">
    <action
    android:id="@+id/action_chooseRecipient_to_chooseAmountFragment"
    app:destination="@id/chooseAmountFragment" />
    </fragment>
    <fragment
    android:id="@+id/chooseAmountFragment"
    android:name="com.example.cashdog.cashdog.ChooseAmountFragment"
    android:label="fragment_choose_amount"
    tools:layout="@layout/fragment_choose_amount" />
    </navigation>
    </navigation>
  6. 在您的代码中,将连接根图的操作的资源ID传递给嵌套图:

    1
    view.findNavController().navigate(R.id.action_mainFragment_to_sendMoneyGraph)
  7. 返回 Design 选项卡,可以通过单击 Root 返回到根图 。

使用引用其他导航图

在导航图中,您可以使用include引用其他图形。 虽然这在功能上与使用嵌套图相同,但include允许您使用其他项目模块或库项目中的图形,如下例所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!-- (root) 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/fragment">

<include app:graph="@navigation/included_graph" />

<fragment
android:id="@+id/fragment"
android:name="com.example.myapplication.BlankFragment"
android:label="Fragment in Root Graph"
tools:layout="@layout/fragment_blank">
<action
android:id="@+id/action_fragment_to_second_graph"
app:destination="@id/second_graph" />
</fragment>

...
</navigation>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- included_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/second_graph"
app:startDestination="@id/includedStart">

<fragment
android:id="@+id/includedStart"
android:name="com.example.myapplication.IncludedStart"
android:label="fragment_included_start"
tools:layout="@layout/fragment_included_start" />
</navigation>

数据传递

导航允许您通过定义目标的参数将数据附加到导航操作。例如,用户配置文件目标可能会使用用户ID参数来确定要显示的用户。

通常,您应该更倾向于仅在目标之间传递最少量的数据。例如,您应该传递一个键来检索一个对象而不是传递该对象本身,因为所有已保存状态的总空间在Android上是有限的。如果需要传递大量数据,请考虑使用片段之间共享数据中ViewModel所述的方法 。

定义目标参数

要在目标之间传递数据,首先通过以下步骤将参数添加到接收它的目标来定义参数:

  1. 在导航编辑器中,单击接收参数的目标。
  2. 在 Attributes 面板中,单击 Add(+)。
  3. 在出现的 Add Argument Link 窗口中,输入参数名称,参数类型,参数是否可为空,以及默认值(如果需要)。
  4. 单击 Add 。请注意,该参数现在显示 在 Arguments 面板的 Attributes 列表中。
  5. 接下来,单击将您带到此目的地的相应操作。在 Attributes 面板中,您现在应该在 Argument Default Values 部分中看到新添加的参数。
  6. 您还可以看到该参数是以XML格式添加的。单击 Text 选项卡以切换到XML视图,并注意您的参数已添加到接收参数的目标。一个例子如下所示:
1
2
3
4
5
6
<fragment android:id="@+id/myFragment" >
<argument
android:name="myArg"
app:argType="integer"
android:defaultValue="0" />
</fragment>

覆盖操作中的目标参数

目标级参数和默认值由导航到目标的所有操作使用。如果需要,您可以通过在操作级别定义参数来覆盖参数的默认值(如果尚不存在,则设置一个参数)。此参数必须与目标中声明的参数 具有相同的名称和类型 。

下面的XML声明了一个带有参数的操作,该参数覆盖了上面示例中的目标级参数:

1
2
3
4
5
6
7
<action android:id="@+id/startMyFragment"
app:destination="@+id/myFragment">
<argument
android:name="myArg"
app:argType="integer"
android:defaultValue="1" />
</action>

使用Safe Args传递类型安全的数据

Navigation Architecture Component有一个名为Safe Args的Gradle插件,它生成简单的对象和构建器类,以便对目标和操作指定的参数进行类型安全访问。

要使用Safe Args,请先将androidx.navigation.safeargs插件添加到应用程序的build.gradle文件中,如下所示:

1
2
3
4
5
6
apply plugin: 'com.android.application'
apply plugin: 'androidx.navigation.safeargs'

android {
//...
}

例外还需在项目build.gradle中添加:

1
classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0-alpha09"

启用插件后,生成的代码包含操作的其他简单对象和构建器类以及发送和接收目标。这些类描述如下:

  • 为动作发起的每个目标创建一个类。此类的名称是始发目标的名称,后面附加 “Directions” 一词。例如,如果始发目标是已命名的片段,SpecifyAmountFragment则将调用生成的类 SpecifyAmountFragmentDirections。
  • 此类具有针对始发目标中定义的每个操作的方法。
  • 对于用于传递参数的每个操作,将创建一个内部类,其名称基于操作。例如,如果调用confirmationAction,该操作 ,则命名该类ConfirmationAction。
  • 为接收目标创建一个类。此类的名称是目标的名称,后面附加单词“Args”。例如,如果命名目标片段,ConfirmationFragment,则调用生成的类ConfirmationFragmentArgs。使用此类的fromBundle()方法来检索参数。

以下示例说明如何使用这些方法设置参数并将其传递给navigate() 方法:

1
2
3
4
5
6
override fun onClick(v: View) {
val amountTv: EditText = view!!.findViewById(R.id.editTextAmount)
val amount = amountTv.text.toString().toInt()
val action = SpecifyAmountFragmentDirections.confirmationAction(amount)
v.findNavController().navigate(action)
}

在接收目标的代码中,使用该 getArguments()方法检索包并使用其内容:

1
2
3
4
5
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val tv: TextView = view.findViewById(R.id.textViewAmount)
val amount = ConfirmationFragmentArgs.fromBundle(arguments).amount
tv.text = amount.toString()
}
使用具有全局操作的Safe Args

使用具有全局操作的 Safe Args时 ,必须为android:id根元素提供值,如以下示例所示:

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_nav"
app:startDestination="@id/mainFragment">

...

</navigation>

导航为基于该值Directions的元素生成一个类android:id。例如,如果您有一个 元素android:id=@+id/main_nav,则调用生成的类 MainNavDirections。元素中的所有目标都扩展了MainNavDirections类,并且可以使用与上一节中描述的方法相同的方法访问所有关联的全局操作。

使用Bundle对象在目标之间传递数据

如果您不使用Gradle,则仍可以使用Bundle对象在目标之间传递参数。使用创建Bundle对象并将其传递到目标navigate(),如下所示

1
2
var bundle = bundleOf("amount" to amount)
view.findNavController().navigate(R.id.confirmationAction, bundle)

在接收目标的代码中,使用该 getArguments()方法检索Bundle并使用其内容:

1
2
val tv = view.findViewById<TextView>(R.id.textViewAmount)
tv.text = arguments.getString("amount")

支持

添加对新目标类型的支持
NavControllers依靠一个或多个Navigator对象来执行导航操作。默认情况下,所有 NavControllers支持通过使用ActivityNavigator 类及其嵌套 ActivityNavigator.Destination 类导航到另一个活动而离开导航图 。为了能够导航到任何其他类型的目标,Navigator必须将一个或多个其他对象添加到 NavController。例如,当使用片段作为目标时,会 NavHostFragment 自动将FragmentNavigator 类添加到其中 NavController。

要向a添加新的Navigator对象 NavController,必须使用相应的Navigator类的 getNavigatorProvider()方法,然后使用类的addNavigator()方法。以下代码显示了将虚构CustomNavigator 对象添加到以下内容的示例NavController:

1
2
val customNavigator = CustomNavigator()
navController.navigatorProvider += customNavigator

大多数Navigator 类都有一个嵌套的目标子类。此子类可用于指定目标唯一的其他属性。有关Destination子类的详细信息,请参阅相应Navigator类的参考文档 。

条件导航

您的应用程序可能有一系列条件目标,这些目标仅在某些条件下使用,例如当用户需要登录时。这些目标应创建为单独的目标,或嵌套导航图,另一个目标作为另一个目标启动需要。图1显示了导航到配置文件目的地的用户,该配置文件目的地确定用户未登录后,要求用户导航到登录目的地。登录目的地然后在登录完成后将用户返回到配置文件目的地。

登录目标应在返回到配置文件目标后从导航堆栈中弹出。popBackStack() 导航回原始目的地时调用 方法。原始目标将从导航堆栈“弹出”并变为活动状态。

全局导航

您可以使用全局操作来创建多个目标可以使用的公共操作。例如,您可能希望不同目的地的按钮导航到同一主应用程序屏幕。

导航编辑器中的一个全局操作由指向关联目标的小箭头表示,如图1所示
导入嵌套图的全局操作

创建一个全局行动

要创建全局操作,请执行以下操作:

  1. 在“ 曲线图编辑器”中,单击目标以突出显示它。
  2. 右键单击目标以显示上下文菜单。
  3. 选择添加操作>全局。箭头()出现在目的地的左侧。
  4. 单击“ 文本”选项卡以导航到XML文本视图。全局操作的XML类似于以下内容:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <?xml version="1.0" encoding="utf-8"?>
    <navigation xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/main_nav"
    app:startDestination="@id/mainFragment">

    ...

    <action android:id="@+id/action_global_mainFragment"
    app:destination="@id/mainFragment"/>

    </navigation>

使用全局操作

要在代码中使用全局操作,请将全局操作的资源ID传递navigate() 给每个UI元素的方法,如以下示例所示:

1
2
3
viewTransactionButton.setOnClickListener { view ->
view.findNavController().navigate(R.id.action_global_mainFragment)
}

迁移到导航

该NavController导航图及其导航图包含在单个活动中。因此,在迁移现有项目以使用导航架构组件时,请通过为每个活动中的目标创建导航图,重点关注一次迁移一个活动。
活动及其各个导航图
然后,可以通过向导航图添加活动目标来链接单独的活动,从而替换startActivity()整个代码库中的现有用法。

一个Activity中的导航图指向第二个Activity
在多个活动共享相同布局的情况下,可以组合导航图,将导航调用替换为活动目标,以直接在两个导航图之间导航调用。

包含组合导航图的活动。

参考:
[1]. 官网 https://developer.android.com/topic/libraries/architecture/navigation/
[2]. 官网demo https://github.com/googlecodelabs/android-navigation
[3]. 官网教程 https://codelabs.developers.google.com/codelabs/android-navigation/#0
[4]. 关于navigation的解析 https://www.jianshu.com/p/ad040aab0e66