开始使用 Navigation

1,771 阅读11分钟

@[toc] 由于官网暂时没有中文版本,所以在这里根据内容抽空做了一些中文的版本.

原文首发于https://leonchen1024.com/2019/07/04/Navigation-Getting-Started/#more

开始使用

依赖引入

dependencies {
    def nav_version = "2.1.0-alpha05"

    implementation "androidx.navigation:navigation-fragment:$nav_version" // For Kotlin use navigation-fragment-ktx
    implementation "androidx.navigation:navigation-ui:$nav_version" // For Kotlin use navigation-ui-ktx
}

最新版本清关注官方版本 developer.android.google.cn/jetpack/and…

创建一个导航图

Navigation 发生在 app 内目的地之间的跳转.这些目的地通过action 进行连接.

一个导航图是一个包含了你所有目的地和 action 的资源文件.这个图表代表了你所有的导航路径.

下图是一个导航图的示例,

原文首发于https://leonchen1024.com/2019/07/04/Navigation-Getting-Started/#more

图中的 1 是 app 中的不同的内容

2 是导航的路径

通过以下步骤添加导航图

  1. 在项目窗口中右键 res 文件夹选择 New > Android Resource File
  2. 输入文件名
  3. Resource type 中选择 Navigation ,确认.

生成的文件会保存在 navigation 文件夹下面.

Navigation Editor

当你添加了导航图的时候,可以使用 Navigation Editor 来进行可视化操作,或者对 xml 文件直接进行修改

图中序号代表的东西 :

1: 目的地面板, 列出你的导航所有者和当前导航图中所有的目的地.

2: 图编辑器 , 可以通过下方的 tab 切换成可视化编辑和文本编辑模式.

3: 属性 , 展示当前选择项的属性.

当你使用文本编辑的时候,应该会看到类似以下的结构:

<?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"
            android:id="@+id/nav_graph">

</navigation>

节点是根节点,当你添加了目的地和 action 的时候它们对应的和都会以 的子元素的形式展现出来. 如果你使用了嵌套图的时候,它会以子的形式展现.

添加一个 NavHost 到 activity

Navigation 模块中的一个核心部分就是 navigation host . navigation host 是一个空的容器,当用户在你的 app 内导航的时候目的地将会在这个容器中进出.

一个 navigation host 必须从 NavHost 中衍生而来.模块中有一个默认的实现,NavHostFragment , 处理了 fragment 目的地的转场.

注意: Navigation 模块的设计是为了处理一个 Activity 和多个目的地Fragment 的情况,每一个 Activity 都会关联一个导航图并拥有自己的NavHostFragment 来响应内容的切换转场.

通过 xml 添加一个NavHostFragment

下面的 xml 展示了一个 NavHostFragment 作为主 activity 的一部分的情况:

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

    <com.google.android.material.bottomnavigation.BottomNavigationView
        .../>

</android.support.constraint.ConstraintLayout>

注意以下内容:

  • android:name 属性包含了你的 NavHost 实现的类名
  • app:navGraph 属性将 NavHostFragment 和一个 导航图进行关联,导航图会显示在这个 NavHostFragment 中用户能到达的所有目的地.
  • app:defaultNavHost="true" 属性确保你的 NavHostFragment 可以拦截系统的返回事件,要注意只有一个 NavHost 可以作为默认拦截.

你也可以通过以下方法来通过布局编辑器来添加一个 NavHostFragment 到 activity 中.

  • 打开你的 activity 的 xml 文件,并使用布局编辑器.
  • Palette 面板中选择Containers目录,或者搜索 "NavHostFragment"
  • 拖动 NavHostFragment 到你的布局中.
  • Navigation Graphs 的弹窗中,选择对应的导航图和 NavHostFragment 关联,并点 ok.

原文首发于https://leonchen1024.com/2019/07/04/Navigation-Getting-Started/#more

添加目的地到导航图

你可以在 Fragment 或者 activity 中添加目的地,也可以通过导航编辑器来添加目的地或者占位符以便在后面用 activity 或 Fragment 来替换.

从现有的 activity 或 Fragment 中添加目的地

如果你想添加现有的目的地到你的导航图中,点击 New Destination

img
, 再出现的下拉框中选择你想要添加的目的地.然后你就可以在 Design 视图中看到该目的地的预览图.以及对应的xml 数据出现在你的 Text 视图中.

创建一个新的Fragment目的地

使用以下步骤创建一个新的 Fragment 目的地:

  1. 在导航编辑器中点击 New Destination图标
    img
    , 然后点击 Create new destination .
  2. New Android Component 弹窗中创建你的 Fragment ,

从 DialogFragment 中创建一个目的地

如果你已经有了 DialogFragment 的话,你可以使用 <dialog> 元素将它添加到导航图中.代码如下:

<?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"
            android:id="@+id/nav_graph">

...


<dialog
    android:id="@+id/my_dialog_fragment"
    android:name="androidx.navigation.myapp.MyDialogFragment">
    <argument android:name="myarg" android:defaultValue="@null" />
        <action
            android:id="@+id/myaction"
            app:destination="@+id/another_destination"/>
</dialog>

...

</navigation>

占位目的地

你可以使用占位符来代表没有实现的目的地,占位符也是目的地的视觉表示.在导航编辑器中可以像使用目的地一样使用占位符.

注意:在你运行你的 app 之前你必须要将 class 设置成已经存在的目的地.否则会导致运行时异常.

解析目的地

点击任意的目的地,并注意以下属性:

  • Type : 表明这个目的地是使用 fragment, activity, 还是其他源码中的自定义类实现.
  • Label : 目的地的 xml 资源文件名
  • ID: 目的地的 ID ,可以用于在代码中引用该目的地
  • Class : 下拉窗中展示了所有与该目的地相关联的类.可以选择不同的选项将类与其他目的地类型关联.

在 xml 文件中的展示如下:

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

设置一个屏幕作为起始目的地

起始目的地是用户打开你的app看到的第一个屏幕,并且也是用户退出你的app看到的最后一个页面.导航编辑器使用一个房子图标

img
来代表这个起始目的地.

一旦你拥有所有的目的地,你可以通过以下步骤选择一个作为起始目的地:

  1. Design 标签页面下,点击一个目的地使它高亮.
  2. 点击 Assign start destination 按钮
    img
    .或者,右键目的地并点击 Set as Start Destination.

原文首发于https://leonchen1024.com/2019/07/04/Navigation-Getting-Started/#more

连接目的地

action 是目的地之间的一个逻辑连接.action 在导航图里用箭头表示.action 通常将一个目的地连接到另一个,尽管你也可以创建一个 global actions 来把你引导到你的app中的任意一个目的地.

通过action ,你可以用不同的路径来代表用户可以如何在你的app中导航.要注意,要实际导航到某个目的地的时候,你仍然需要编写代码来实现这个导航.这在 Navigate to a destination 中会有介绍.

你可以通过以下步骤使用导航编辑器来连接两个目的地:

  1. Design 标签下,高亮你要作为起点的目的地.它的右侧会出现一个圆圈

    img

  2. 点击这个圆圈并拖动鼠标指针到你想要作为终点的目的地,然后释放鼠标.两个目的地之间会出现一条线来代表这个action

    img

  3. 点击箭头来高亮这个 action . Attributes 面板中显示了下面的属性:

    • Type : Action 的类型
    • ID : action的ID
    • Destination : 包含了目的地fragment 或者 activity 的 ID
  4. 点击 Text 标签来切换到 XML 视图. 一个 action 元素会被添加到源目的地中.这个action拥有一个 ID 和一个目的地属性包含了下一个目的地的ID 如下所示:

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

在你的导航图里,action是使用 <action> 元素代表的. 一个 action 里至少要包含它自己的ID 和用户要导航到的目的地的ID .

原文首发于https://leonchen1024.com/2019/07/04/Navigation-Getting-Started/#more

导航到目的地

可以使用 NavController 导航到一个目的地,它是一个通过 NavHost 来管理app导航的对象.每一个 NavHost 都有一个它对应的 NavController . NavController 提供一些不同的方式来导航到一个目的地,在下面的章节中将会详细介绍.

使用以下方法之一来从 fragment ,activity,或者view 中获取一个 NavController :

Kotlin:

Java:

使用 ID 导航

navigate(int) 使用 action 或者目的地的资源 ID来导航. 下面的例子展示了如何导航到 ViewTransactionsFragment .

viewTransactionsButton.setOnClickListener { view ->
   view.findNavController().navigate(R.id.viewTransactionsAction)
}
viewTransactionsButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        Navigation.findNavController(view).navigate(R.id.viewTransactionsAction);
    }
});

注意: 当你使用 ID 来导航的时候,我们强烈推荐你尽可能使用 action. action 在你的导航图里提供了额外的可视信息,它展示了你的目的地是如何和其它目的地连接起来的.通过创建 action ,你可以使用 Safe Args-generated operations 来替换资源ID ,提供额外的编译时安全性.通过使用 action 你也可以在目的地之间使用转场动画.详细可以查看 Animate transitions between destinations.

对于按钮,你也可以使用 Navigation 类的 createNavigateOnClickListener() 方法来便捷的导航到一个目的地,如下面的例子所示:

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

为了处理其它的共用 UI 模块,比如顶部的 top app bar 和 bottom navigation , 详见 Update UI components with NavigationUI.

通过 URI 进行导航

你可以通过使用 navigate(Uri) 来直接导航到一个 implicit deep link destination, 如下:

val navController = findNavController()
val deeplink = Uri.parse("android-app://androidx.navigation.app/profile")
findNavController().navigate(deeplink)
Uri deeplink = Uri.parse("android-app://androidx.navigation.app/profile");
view.findNavController().navigate(deeplink);

和使用 action 或者 目的地ID 不同,你可以导航到你的导航图中的任意一个 URI ,无论目的地是否在当前导航图中可见.你可以导航到当前导航图或者一个不同的导航图中的目的地.

当使用 URI 进行导航的时候,回退栈不会被重置.与其他的 deep link navigation 不同,

When navigating using URI, the back stack is not reset. This is unlike other deep link navigation, where the back stack is replaced when navigating. popUpTo and popUpToInclusive, however, still remove destinations from the back stack just as though you had navigated using an ID.

导航还有回退栈

Android 维护了一个 back stack 包含了你访问的目的地.你app 的第一个 目的地在用户打开 app 的时候就会放进栈中. 每一次调用 navigate() 方法会把另一个目的地放到栈顶. 当调用 NavController.navigateUp()NavController.popBackStack() 来进行向上或向后的操作的时候,会移除栈顶的目的地.

popUpTo 和 popUpToInclusive

当你使用 action 来进行导航的时候,你可以选择从后退栈中弹出额外的目的地.举个例子,如果你的应用有一个登录流程,一旦用户登录了,你应该从回退栈中弹出所有登录相关的目的地,这样可以保证用户回退的时候不会又回到登录流程中.

为了在一个目的地导航到另一个目的地的时候弹出目的地,可以添加一个 app:popUpTo 属性到关联的 <action> 元素中. app:popUpTo 这个属性告诉 Navigation 在调用 navigate() 的时候从回退栈中弹出一些目的地.这个属性的值是 要在回退中保留的顶层目的地的ID .

你也可以使用 app:popUpToInclusive="true" 来声明这个 app:popUpTo 弹出操作包含了这个属性声明的ID 对象.

popUpTo 示例:循环逻辑

我们假设你的app拥有3个目的地---A,B,C.还有从 A 到 B ,B 到 C ,以及 C 回到A 的action.对应的导航图如下:

img

当你每一次调用导航action的时候,都会往回退栈中添加一个目的地.如果你按照这个流程重复导航的花,你的回退栈中将会包含很多这个重复的集合目的地(A,B,C,A,B,C,A....) . 要避免这种重复的话,可以在从C 到A 的action中指定 app:popUpTo 还有 app:popUpToInclusive 属性,如下:

<fragment
    android:id="@+id/c"
    android:name="com.example.myapplication.C"
    android:label="fragment_c"
    tools:layout="@layout/fragment_c">

    <action
        android:id="@+id/action_c_to_a"
        app:destination="@id/a"
        app:popUpTo="@+id/a"
        app:popUpToInclusive="true"/>
</fragment>

在到达了目的地 C 之后,回退栈中包含了目的地 A,B,C 的实例.当导航回目的地 A 的时候,我们同时 popUpTo A ,这意味着我们在导航的时候从回退栈中删除了 B,C. 伴随着 app:popUpToInclusive="true" 我们同时弹出了回退栈中的第一个 A.要注意如果你没有使用 app:popUpToInclusive="true" 这个属性的话,你的回退栈中将会包含两个A 的实例.

More information

If you encounter any issues with Navigation, please submit feedback via one of the following channels:

For information on how to provide the most helpful information in bug reports, see the following links:

原文首发于https://leonchen1024.com/2019/07/04/Navigation-Getting-Started/#more

Refernce

developer.android.google.cn/guide/navig…

About Me

我的博客 leonchen1024.com

我的 GitHub github.com/LeonChen102…

微信公众号

在这里插入图片描述