本文最后更新于:1 个月前
导航是指支持用户导航、进入和退出应用中不同内容片段的交互。
组成
导航组件由以下三个关键部分组成:
- 导航图 res/navigation/ 目录下文件
NavHost
:显示导航图中目标的空白容器。导航组件包含一个默认NavHost
实现(NavHostFragment
),可显示 Fragment 目标。NavController
:在NavHost
中管理应用导航的对象。当用户在整个应用中移动时,NavController
会安排NavHost
中目标内容的交换。
创建导航图
导航图是一种资源文件,其中包含所有目的地和操作。该图表会显示应用的所有导航路径。
要向项目添加导航图,请执行以下操作:
- 在 “Project” 窗口中,右键点击
res
目录,然后依次选择 New > Android Resource File。此时系统会显示 New Resource File 对话框。 - 在 File name 字段中输入名称,例如 “nav_graph”。
- 从 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
- 在项目文件列表中,双击 Activity 的布局 XML 文件,以在 Layout Editor 中将其打开。
- 在 Palette 窗格内,选择 Containers 类别。
- 将 NavHostFragment 视图拖动到 Activity 上。
- 在随即显示的 Navigation Graphs 对话框中,选择要与此
NavHostFragment
相关联的对应导航图,然后点击 OK。
将某个屏幕指定为起始目的地
- 在 Design 标签页中,点击相应目的地,使其突出显示。
- 点击 Assign start destination(小房子) 按钮 。或者,您可以右键点击该目的地,然后点击 Set as Start Destination。
设计导航图
嵌套图表
要将目的地归入一个嵌套图表中,操作如下:
- 在 Navigation Editor 中,按住
shift
键,然后点击想要添加到嵌套图表中的目的地。 - 右键单击以打开上下文菜单,然后依次选择 Move to Nested Graph > New Graph。目的地包含在嵌套图表中。
在代码中,传递将根图连接到嵌套图表的操作的操作的资源ID:
Navigation.findNavController(view).navigate(ID);
通过 <include>
引用其他导航图表
<include app:graph="@navigation/included_graph" />
全局操作
对于应用中的任何可通过多条路径到达的目的地,您都应定义可转到它的相应全局操作。全局操作可用于从任意位置导航到某目的地。
创建全局操作
要创建全局操作,请执行以下操作:
- 在 Graph Editor 中,点击一个目的地,使其突出显示。
- 右键点击该目的地,以显示上下文菜单。
- 依次选择 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 进行导航时,返回堆栈不会重置。这与其他深层链接导航不同,后者在导航时会替换返回堆栈。不过,popUpTo
和 popUpToInclusive
仍会从返回堆栈中移除目的地,就像您使用 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
中指定的目的地也应从返回堆栈中移除。
示例(有助于理解)。
条件导航
在为应用设计导航时,可能需要基于条件逻辑将用户转到某一个目的地而非另一个。例如,可能具有一些需要用户登录的目的地,或者可能在游戏中针对获胜或失败的玩家提供了不同的目的地。
在目的地间传递数据
定义目的地参数
要在目的地之间传递数据,首先按照以下步骤将参数添加到接收它的目的地来定义参数:
- 在 Navigation Editor 中,点击接收参数的目的地。
- 在 Attributes 面板中点击 Arguments 处的 Add(+)。
- 在弹出的面板中输入 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)
- 使用图表 ID:
要检索起始目的地中的数据,请调用 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();
创建工具栏
- 在主 Activity 中定义工具栏
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/tool_bar"
tools:ignore="MissingConstraints" />
- 通过 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
的所有顶级目的地上。顶级目的地是应用的根级目的地。它们不会在应用栏中显示向上按钮。
- 声明
DrawerLayout
为根视图。 - 将
DrawerLayout
连接到导航图,具体方法是将其传递给AppBarConfiguration
,如下所示:
AppBarConfiguration appBarConfiguration =
new AppBarConfiguration.Builder(navController.getGraph())
.setDrawerLayout(drawerLayout)
.build();
- 在主 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);
}
底部导航栏
- 在主 Activity 中定义底部导航栏。
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_nav"
app:menu="@menu/menu_bottom_nav" />
- 在主 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)
如果 MenuItem
的 id
与目的地的 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 协议 ,转载请注明出处!