本文最后更新于:1 个月前

导航是指支持用户导航、进入和退出应用中不同内容片段的交互。

组成

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

  • 导航图 res/navigation/ 目录下文件
  • NavHost:显示导航图中目标的空白容器。导航组件包含一个默认 NavHost 实现(NavHostFragment),可显示 Fragment 目标。
  • NavController:在 NavHost 中管理应用导航的对象。当用户在整个应用中移动时,NavController 会安排 NavHost 中目标内容的交换。

创建导航图

导航图是一种资源文件,其中包含所有目的地和操作。该图表会显示应用的所有导航路径。
要向项目添加导航图,请执行以下操作:

  1. 在 “Project” 窗口中,右键点击 res 目录,然后依次选择 New > Android Resource File。此时系统会显示 New Resource File 对话框。
  2. File name 字段中输入名称,例如 “nav_graph”。
  3. Resource type 下拉列表中选择 Navigation,然后点击 OK。

当您添加首个导航图时,Android Studio 会在 res 目录内创建一个 navigation 资源目录。该目录包含您的导航图资源文件(例如 nav_graph.xml)。

向Activity添加NavHost

通过XML添加NavHostFragment

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

    <androidx.appcompat.widget.Toolbar
        .../>

    <fragment
        android:id="@+id/nav_host_fragment"
        <!-- NavHost实现的类名称 -->
        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"
        <!-- 确保NavHostFragment会拦截系统返回按钮。只能有一个默认NavHost。如果同一布局中有多个主机,务必仅指定一个默认NavHost。 -->
        app:defaultNavHost="true"
        <!-- 将NavHostFrament与导航图相关联。导航图会在此NavHostFragment中指定用户可以导航到的所有目的地。
        app:navGraph="@navigation/nav_graph" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        .../>
</android.support.constraint.ConstraintLayout>

使用 Layout Editor 向 Activity 添加 NavHostFragment

  1. 在项目文件列表中,双击 Activity 的布局 XML 文件,以在 Layout Editor 中将其打开。
  2. Palette 窗格内,选择 Containers 类别。
  3. 将 NavHostFragment 视图拖动到 Activity 上。
  4. 在随即显示的 Navigation Graphs 对话框中,选择要与此 NavHostFragment 相关联的对应导航图,然后点击 OK。

将某个屏幕指定为起始目的地

  1. Design 标签页中,点击相应目的地,使其突出显示。
  2. 点击 Assign start destination(小房子) 按钮 。或者,您可以右键点击该目的地,然后点击 Set as Start Destination

设计导航图

嵌套图表

要将目的地归入一个嵌套图表中,操作如下:

  1. 在 Navigation Editor 中,按住 shift 键,然后点击想要添加到嵌套图表中的目的地。
  2. 右键单击以打开上下文菜单,然后依次选择 Move to Nested Graph > New Graph。目的地包含在嵌套图表中。
    在代码中,传递将根图连接到嵌套图表的操作的操作的资源ID:
Navigation.findNavController(view).navigate(ID);

通过 <include> 引用其他导航图表

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

全局操作

对于应用中的任何可通过多条路径到达的目的地,您都应定义可转到它的相应全局操作。全局操作可用于从任意位置导航到某目的地。

创建全局操作

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

  1. Graph Editor 中,点击一个目的地,使其突出显示。
  2. 右键点击该目的地,以显示上下文菜单。
  3. 依次选择 Add Action > Global。此时系统会在该目的地左侧显示一个箭头。

导航到目的地

导航到目的地是使用 NavController 完成的,后者是一个在 NavHost 中管理应用导航的对象。每个 NavHost 均有自己的相应 NavController 。可以使用以下方法之一检索 NavController

Kotlin

  • Fragment.findNavController()
  • View.findNavController()
  • Activity.findNavController(viewId: Int)

Java

  • NavHostFragment.findNavController(Fragment)
  • Navigation.findNavController(Activity, @IdRes int viewId)
  • Navigation.findNavController(View)

检索 NavController 之后,可以调用navigate()的某个重载,以在各个目的地之间导航。

使用 ID 导航

对于按钮,可以使用 Navigation 类的 createNavigateOnClickListener(ID) 导航到目的地。

为导航提供导航选项

在导航图中定义操作时,Navigation 会生成相应的 NavAction 类,其中包含为该操作定义的配置,包括如下内容:

  • 目的地:目标目的地的资源 ID。
  • 默认参数:android.os.Bundle,包含目标目的地的默认值(如有提供)。
  • 导航选项:导航选项,表示为 NavOptions 。此类包含从目标目的地往返的所有特殊配置,包括动画资源配置、弹出行为以及是否应在单一顶级模式下启动目的地。

使用URI导航

使用 URI 进行导航时,返回堆栈不会重置。这与其他深层链接导航不同,后者在导航时会替换返回堆栈。不过,popUpTopopUpToInclusive 仍会从返回堆栈中移除目的地,就像您使用 ID 导航一样。

导航和返回堆栈

每次调用 navigate() 方法都会将另一目的地放置到堆栈的顶部。点按向上返回会分别调用 NavController.navigateUp()NavController.popBackStack() 方法,用于移除(或弹出)堆栈顶部的目的地。
NavController.popBackStack() 会返回一个布尔值,表明它是否已成功返回到另一个目的地。当返回 false 时,最常见的情况是手动弹出导航表的起始目的地。
如果该方法返回 false,则 NavController.getCurrentDestination() 会返回 null 。此时应导航到新目的地,或通过对 Activity 调用 finish() 来处理弹出情况,如下例所示:

if (!navController.popBackStack()) {
    // Call finish() on your Activity
    finish();
}

popUpTo 和 popUpToInclusive

使用操作进行导航时,可以选择从返回堆栈上弹出其他目的地。例如,如果应用具有初始登录流程,那么在用户登录后,应将所有与登录相关的目的地从返回堆栈上弹出,这样返回按钮就不会将用户带回登录流程。
要在从一个目的地导航到另一个目的地时弹出目的地,可以在关联的 <action> 元素中添加 app:popUpTo 属性。app:popUpTo 会告知 Navigation 库在调用 navigate() 的过程中从返回堆栈上弹出一些目的地。属性值是应保留在堆栈中的最新目的地的 ID。
还可以添加 app:popUpToInclusive="true",以表明在 app:popUpTo 中指定的目的地也应从返回堆栈中移除。
示例(有助于理解)。

条件导航

在为应用设计导航时,可能需要基于条件逻辑将用户转到某一个目的地而非另一个。例如,可能具有一些需要用户登录的目的地,或者可能在游戏中针对获胜或失败的玩家提供了不同的目的地。

在目的地间传递数据

定义目的地参数

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

  1. 在 Navigation Editor 中,点击接收参数的目的地。
  2. Attributes 面板中点击 Arguments 处的 Add(+)。
  3. 在弹出的面板中输入 Name、Type、参数是否为 Array、参数是否可为 null(不论基础类型的 null 性如何,数组始终可为 null),以及 Default Value(若需要),然后点击 Add

使用 Safe Args 传递安全的数据

要将Safe Args添加到项目,首先在顶级 build.gradle 文件中包含以下 classpath

buildscript {
    repositories {
        google()
    }
    dependencies {
        def nav_version = "2.3.0-alpha01"
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
    }
}

同时必须应用以下两个可用插件之一。
要生成适用于 Java 或 Java 和 Kotlin 混合模块的 Java 语言代码,请将以下行添加到应用或模块的 build.gradle 文件中:

apply plugin: "androidx.navigation.safeargs"

此外,要生成适用于 Kotlin 独有的模块的 Kotlin 代码,请添加以下行:

apply plugin: androidx.navigation.safeargs.kotlin

将Safe Args用于全局操作

将 Safe Args 用于全局操作时,必须为根 <navigation> 元素提供一个 android:id 值。Navigation会根据 android:id 值为 <navigation> 元素生成一个 Directions 类。

使用Bundle对象在目的地之间传递参数

传递

Bundle bundle = new Bundle();
bundle.putString("amount", "amount");
Navigation.findNavController(view).navigate(ID, bundle);

接收

String amount = getArguments().getString("amount");

将数据传递给起始目的地

您可以将数据传递给应用的起始目的地。首先,您必须显式构建一个 Bundle 来存储数据。然后,使用以下方法之一将该 Bundle 传递给起始目的地:

  • 如果您要以编程方式创建 NavHost,请调用 NavHostFragment.create(R.navigation.graph, args),其中 args 是存储数据的 Bundle
  • 或者,您也可以通过调用以下 NavController.setGraph() 过载之一来设置起始目的地参数:
    • 使用图表 ID:navController.setGraph(R.navigation.graph, args)
    • 使用图表本身:navController.setGraph(navGraph, args)

要检索起始目的地中的数据,请调用 Fragment.getArguments()

为目的地创建深层链接

创建显示深层链接

当用户通过显式深层链接打开您的应用时,任务返回堆栈会被清除,并被替换为相应的深层链接目的地。当嵌套图表时,每个嵌套级别的起始目的地(即层次结构中每个 <navigation> 元素的起始目的地)也会添加到相应堆栈中。也就是说,当用户从深层链接目的地按下返回按钮时,他们会返回到相应的导航堆栈,就像从入口点进入您的应用一样。

您可以使用 NavDeepLinkBuilder 类构建 PendingIntent,如下例所示。请注意,如果提供的上下文不是 Activity,构造函数会使用PackageManager.getLaunchIntentForPackage() 作为默认Activity 来启动(如果有)。

PendingIntent pendingIntent = new NavDeepLinkBuilder(context)
    .setGraph(R.navigation.nav_graph)
    .setDestination(R.id.android)
    .setArguments(args)
    .createPendingIntent();

如果已有 NavController,则还可以通过 NavController.createdDeepLink() 创建深层链接。

创建隐式深层链接

创建隐式深层链接

在目的地之间添加动画过渡效果

在目的地之间添加动画过渡效果

使用NavigationUI更新界面组件

监听导航事件

NavController 提供 OnDestinationChangedListener() 接口,该接口在当前目的地或其参数发生更改时调用。可以通过addOnDestinationChangedListener() 方法注册新监听器。

举例来说,使用此监听器可以在应用的一些区域显示常见界面元素,而在另外一些区域隐藏这些元素。

navController.addOnDestinationChangedListener(new NavController.OnDestinationChangedListener() {
   @Override
   public void onDestinationChanged(@NonNull NavController controller,
           @NonNull NavDestination destination, @Nullable Bundle arguments) {
       if(destination.getId() == R.id.full_screen_destination) {
           toolbar.setVisibility(View.GONE);
           bottomNavigationView.setVisibility(View.GONE);
       } else {
           toolbar.setVisibility(View.VISIBLE);
           bottomNavigationView.setVisibility(View.VISIBLE);
       }
   }
});

顶部应用栏

AppBarConfiguration

NavigationUI 使用 AppBarConfiguration 对象管理在应用显示区域左上角的导航按钮行为。默认情况下,如果用户位于导航图的顶级目的地,则导航按钮会隐藏并且在任何其他目的地显示为向上按钮。
要将导航图的起始目的地用作唯一顶级目的地,可以创建AppBarConfiguration 对象并传入相应的导航图,如下所示:

AppBarConfiguration appBarConfiguration =
        new AppBarConfiguration.Builder(navController.getGraph()).build();

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

AppBarConfiguration appBarConfiguration =
        new AppBarConfiguration.Builder(R.id.main, R.id.android).build();

创建工具栏

  1. 在主 Activity 中定义工具栏
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/tool_bar"
tools:ignore="MissingConstraints" />
  1. 通过 Activity 的 onCreate() 方法调用 setupWithNavController()
@Override
protected void onCreate(Bundle savedInstanceState) {
    setContentView(R.layout.activity_main);

    ...

    NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
    AppBarConfiguration appBarConfiguration =
            new AppBarConfiguration.Builder(navController.getGraph()).build();
    Toolbar toolbar = findViewById(R.id.toolbar);
    NavigationUI.setupWithNavController(toolbar, navController);
}
包含 CollapsingToolbarLayout
NavigationUI.setupWithNavController(layout, toolbar, navController, appBarConfiguration);
操作栏添加导航支持

要向默认操作栏添加导航支持,可通过 Activity 的 onCreate() 方法调用setupActionBarWithNavController()

NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);

接着,替换 onSupportNavigateUp() 以处理向上导航:

@Override
public boolean onSupportNavigateUp() {
    NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
    return NavigationUI.navigateUp(navController, appBarConfiguration)
            || super.onSupportNavigateUp();
}

添加抽屉式导航栏

抽屉式导航栏图标会显示在使用 DrawerLayout所有顶级目的地上。顶级目的地是应用的根级目的地。它们不会在应用栏中显示向上按钮。

  1. 声明 DrawerLayout 为根视图。
  2. DrawerLayout 连接到导航图,具体方法是将其传递给 AppBarConfiguration,如下所示:
AppBarConfiguration appBarConfiguration =
        new AppBarConfiguration.Builder(navController.getGraph())
            .setDrawerLayout(drawerLayout)
            .build();
  1. 在主 Activity 类中,通过主 Activity 的 onCreate() 方法调用setupWithNavController(),如下所示:
@Override
protected void onCreate(Bundle savedInstanceState) {
    setContentView(R.layout.activity_main);

    ...

    NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
    NavigationView navView = findViewById(R.id.nav_view);
    NavigationUI.setupWithNavController(navView, navController);
}

底部导航栏

  1. 在主 Activity 中定义底部导航栏。
<com.google.android.material.bottomnavigation.BottomNavigationView
    android:id="@+id/bottom_nav"
    app:menu="@menu/menu_bottom_nav" />
  1. 在主 Activity 类中,通过主 Activity 的 onCreate() 方法调用 setupWithNavController() ,如下所示:
@Override
protected void onCreate(Bundle savedInstanceState) {
    setContentView(R.layout.activity_main);

    ...

    NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
    BottomNavigationView bottomNav = findViewById(R.id.bottom_nav);
    NavigationUI.setupWithNavController(bottomNav, navController);
}

将目的地(Fragment)关联到菜单项(MenuItem)

如果 MenuItemid 与目的地的 id 匹配,则 NavController 可以导航至该目的地。
菜单项

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

目的地

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

使用 onOptionsItemSelected() 替换Activity的 onCreateOptionsMenu() 以调用 onNavDestinationSelected() 将菜单项与目的地相关联,如下所示:

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
    return NavigationUI.onNavDestinationSelected(item, navController)
            || super.onOptionsItemSelected(item);
}

使用 ViewPager 创建包含标签页的滑动视图

示例链接

以编程方式与导航组件交互

NavHostFragment finalHost = NavHostFragment.create(R.navigation.example_graph);
getSupportFragmentManager().beginTransaction()
    .replace(R.id.nav_host, finalHost)
    .setPrimaryNavigationFragment(finalHost) // this is the equivalent to app:defaultNavHost="true"
    .commit();

请注意,setPrimaryNavigationFragment(finalHost) 允许您的 NavHost 截获对系统“返回”按钮的按下操作。您也可以添加 app:defaultNavHost="true",在 NavHost XML 中实现此行为。如果要实现自定义“返回”按钮行为,并且不希望 NavHost 截获对“返回”按钮的按下操作,您可以将 null 传递给 setPrimaryNavigationFragment()


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!

Resource 上一篇
Retrofit使用 下一篇