传统的应用开发,一般都是采用一个界面一个 Activity
的形式,但是大家都知道, Activity
在 Android 中是属于重量级的组件,从而导致程序资源消耗大,用户体验不佳。而导航组件 Navigation
采用的是 Fragment
轻量级的组件实现的,可以节省资源,提高用户体验。
导航组件是 Android Jetpack 的一部分,主要用途是实现用户导航、进入和退出应用中不同内容片段的交互,不论是普通的按钮点击,还是应用栏、抽屉导航栏等复杂的模式,它都能轻松应对,当然,导航组件也有它既定的 导航原则 来确保一致且可预测的用户体验。
导航组件主要有三部分组成:
navGraph
):这是包含所有导航相关信息的 XML
资源,这些信息包括应用内所有内容区域个体(称为目标,一般都是 Fragment
),以及用户可以通过应用跳转的可能路径。NavHost
):这是用来显示导航图中声明的目标的一个空白容器。导航组件包含一个默认的 NavHost
实现 (NavHostFragment),可用于显示 Fragment
目标。NavController
):在 NavHost
中管理应用导航的目标,当用户在应用中进行操作时,导航控制器会控制目标的切换。使用导航组件有各种优势,包括以下方面:
Fragment
事务;ViewModel
支持在使用导航组件时,应当遵循一些原则,以提高用户体验。
注意:即使您未在项目中使用 Navigation 组件,您的应用也应遵循这些设计原则。
顾名思义,您构建的应用必须有一个固定的起始目的地,这个起始目的地就是指当应用启动时蛋刀的第一个屏幕。起始目的地也是用户按返回按钮后,在回到启动器前看到的最后一个屏幕。
示例:
以上示例中,用户登录页面就是起始目的地,点击启动器图标打开应用,第一个启动的页面就是用户登录页面,在返回过程中,最后一个呈现的页面也是登录页面。
在用户启动应用时,系统会启动一个新任务,并且显示起始目的地,这个起始目的地是应用导航的而基础。当用户在应用中进行导航时,栈顶的目标就是显示在屏幕上的,而栈内的所有目标都是历史记录。
导航组件会为你管理所有返回栈的顺序,当然你也可以自行管理,已达到某些目的。
首先,说一下什么是向上按钮,向上按钮是指在应用中的返回上一级的按钮(一般是在用户导航栏中),返回按钮则是系统导航中的返回按钮。在应用的任务重,向上按钮和返回按钮的行为相同,都是将栈顶的目标移除,返回到上一个目标。
在应用的任务重,向上按钮可以返回到上一个目标,但是绝不会退出应用。
无论是通过深度链接至特定的目的地,还是手动导航到特定目的地,都可以使用向上按钮通过各个目的地导航回到起始目的地。当深度链接至特定的目的地时,会移除所有返回栈中的任务,并替换为深度链接的返回栈。值得注意的是,深度链接合成的返回栈是一个完整的返回栈,他跟手动导航至特定目的地具有相同的返回栈,这个是非常重要的,因为合成的返回栈必须是真实的。
本文对应的Demo源码请访问Github:NavigationDemo
在应用模块目录下的 build.gradle
文件中添加 dependencies
依赖声明。
dependencies {
def nav_version = "2.2.2"
// Java language implementation
implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version"
// Kotlin
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
// Dynamic Feature Module Support
implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version"
// Testing Navigation
androidTestImplementation "androidx.navigation:navigation-testing:$nav_version"
}
导航是发生在各个目的地之间的,而这些目的地通过操作连接在一起。导航图是一种资源文件,它包含了所有的目的地和操作的声明。
创建导航图资源文件,可以按以下不走进行:
res
目录下,右键-》“New”-》“Android Resource File”;
新建的导航图资源文件是一个 XML 资源文件,以 navigation
为根节点,大致内容如下。
<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">
navigation>
元素是导航图的根元素。当您向图表添加目的地和连接操作时,可以看到相应的
和
子元素。如果有嵌套图表,也会出现
子元素。
如果是首次添加导航图资源,Android Studio 会在
res
目录内创建一个navigation
资源目录,该目录包含您的导航图资源文件。当然,如果你已经够熟练,可以直接创建目录和文件的方式创建。
Android Studio提供了强大的导航编辑器,在这里不但可以预览您所添加的目标,还可以修改导航图,可以通过拖动的方式或者直接编码修改底层 XML 的方式修改导航图。为了方便项目的维护和代码可读性,笔者更加建议使用修改底层 XML 的方式,或者结合修改底层 XML 的方式。
温馨提示:不同版本的Android Studio的界面操作有些不一样,不少从旧版升级到3.6之后,发现打开资源文件的时候,默认是 “Design” 模式(包括
layout
布局资源),一时间找不到北了,不知道如何切换成修改底层 XML 的模式。其实这是3.6 版本之后的小变动,在旧版本只有 “Code” 和 “Design” 两种模式,新版有 “Code”、“Split” 、“Design”三种模式,而且模式切换的位置也变了,旧版是在左下角,而新版是在右上角,而且是图标的形式(如下图)。
导航宿主是导航组件的核心部分之一,导航宿主是一个空容器,用来存放和处理目的地。导航宿主必须派生于 NavHost,NavHostFragment 是导航组件的默认导航宿主实现,负责处理 Fragment
目的地的交换。
注意:导航组件的设计理念是用于具有一个主
Activity
和多个Fragment
目的地的应用,主Activity
与导航图相关联,并且包含一个负责根据需要交换目的地的NavHostFragment
。如果您的应用需要在多个Activity
上实现导航,就需要为每个Activity
添加导航宿主,并在每个Activity
关联其自己的导航图。
在主 Activity
的布局文件中,添加
标签,并在内部指定导航图,如下:
<androidx.constraintlayout.widget.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/fragment_main"
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" />
androidx.constraintlayout.widget.ConstraintLayout>
详细解说:
android:name
属性包含 NavHost
实现类的名称(示例中使用的是默认实现 NavHostFragment
,如果有需要,可以使用自定义的Fragment
类,但是必须实现 NavHost
或者继承 NavHostFragment
)app:navGraph
属性将导航宿主(NavHostFragment
)与导航图关联,指向包含所有导航目的地的导航图资源文件app:defaultNavHost="true"
属性确保导航宿主会拦截系统返回按钮。请注意,只能有一个默认导航宿主,如果同一布局(例如,双窗格布局)中有多个导航宿主,请务必仅指定一个默认导航宿主。说明:导航组件是 Android Jetpack 部分,不属于 Android 系统组件,所以需要在布局中添加属性引入,如:
xmlns:app="http://schemas.android.com/apk/res-auto"
除此之外,还可以使用 Layout Editor 向 Activity 添加导航宿主,具体步骤如下:
对于懒于编码的小伙伴可以使用 Navigation Editor 向导航中添加目的地,因为这些都是用户引导模式的,没什么可说,我这里主要讲一下手动添加目的地的步骤:
Fragment
类和布局文件,并实现相关逻辑代码;
标签;
标签的相关属性,如:android:id
、android:name
、android:lable
等;示例:
<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/loginFragment">
<fragment
android:id="@+id/loginFragment"
android:name="com.owen.navigationdemo.LoginFragment"
android:label="LoginFragment"
tools:layout="@layout/fragment_login">
<action
android:id="@+id/action_loginFragment_to_registerFragment"
app:destination="@id/registerFragment"
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>
<fragment
android:id="@+id/registerFragment"
android:name="com.owen.navigationdemo.RegisterFragment"
android:label="RegisterFragment"
tools:layout="@layout/fragment_register">
fragment>
navigation>
目的地属性详解
Type
:即标签名称,指示在源代码中,该目的地是作为 Fragment
、Activity
还是其他自定义类实现的anroid:label
:这个属性指定目的地的名称android:id
: 这个属性指定改目的地的ID,用于在代码中引用该目的地android:name
:这个属性用来指定目的地所关联的类tools:layout
属性指定预览布局文件,这样就可以在导航图编辑器中看到对应的布局预览。 导航的原则之一就是固定的起始目的地,指定起始目的地的方法有两种,一种是使用 Navigation Editor,在 “Design”窗口中,选中需要指定为起始目的地的目标,点击 “房子”图标(如下图)即可。另一种方法就是在 XML 源代码中,在
标签中添加 app:startDestination
属性进行指定,属性值为需要指定的目的地的ID(如下示例)。
示例:通过 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"
android:id="@+id/nav_graph"
app:startDestination="@id/loginFragment">
<fragment
android:id="@+id/loginFragment"
android:name="com.owen.navigationdemo.LoginFragment"
android:label="LoginFragment"
tools:layout="@layout/fragment_login">
<action
android:id="@+id/action_loginFragment_to_registerFragment"
app:destination="@+id/registerFragment"
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>
<fragment
android:id="@+id/registerFragment"
android:name="com.owen.navigationdemo.RegisterFragment"
android:label="RegisterFragment"
tools:layout="@layout/fragment_register">
fragment>
navigation>
目的地之间的逻辑连接也叫做操作,操作一般是将一个目的地连接到另一个目的地,当然,你也可以定义 全局操作 ,这类操作可以在任意位置跳转到指定的目的地,这个我们在后面会详细讲到。
您可以使用 Navigation Editor 连接两个目的地,直接拖动箭头即可,在这里就不多介绍这种方式,直接介绍通过修改 XML 源码的方式(其实使用 Navigation Editor 也会自动修改 XML 源码),具体步骤如下:
标签内部新增
标签;anroid:id
和app:destnation
属性;app:enterAnim
、app:exitAnim
、app:popEnterAnim
、app:popExitAnim
属性定义动画。详细解说:
Type
:即
标签;anroid:id
:这个字段是操作ID,代码中通过这个ID执行操作;app:destnation
:这个字段是操作的目的地,用来指定操作跳转的目的地。
<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/homeFragment">
<fragment
android:id="@+id/homeFragment"
android:name="com.owen.navigationdemo.HomeFragment"
android:label="HomeFragment"
tools:layout="@layout/fragment_home" >
<action
android:id="@+id/action_homeFragment_to_loginFragment"
app:destination="@+id/loginFragment"
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>
navigation>
完成了导航图的各种配置,那么就需要在代码中实现导航到目的地了。导航到目的地是使用 NavController 完成的,这是在导航宿主中管理导航的对象的,每个导航宿主都有自己的相应导航控制器(NavController
)。导航到目的地的步骤如下:
检索导航宿主的导航控制器,可以通过以下方法:
Kotlin:
Java:
说明:Kotlin可以直接在
Fragment
、View
以及Activity
使用findNavController
是因为使用了扩展方法,当然,也可以直接跟Java那样调用对应的接口。
class HomeFragment: Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_home, container, false)
view.findViewById<TextView>(R.id.tvNickname).setOnClickListener {
// Kotlin 扩展方法检索当前导航宿主的导航控制器
val navController = findNavController()
}
return view
}
}
检索到导航控制器之后,使用导航控制器类的 NavController.navigate()
API 导航到指定的目的地,NavController.navigate()
有多个变体,这里就以使用目的地ID进行导航为例:
class HomeFragment: Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_home, container, false)
view.findViewById<TextView>(R.id.tvNickname).setOnClickListener {
// Kotlin 扩展方法检索当前导航宿主的导航控制器
val navController = findNavController()
navController.navigate(R.id.action_homeFragment_to_loginFragment)
}
return view
}
}
返回到指定的目的地,是指返回到之前导航过的目的地,这些目的地必须是在任务栈内的,可以通过 NavController.popBackStack() 接口返回上一级,或者通过 NavController.popBackStack (int destinationId, boolean inclusive) 返回到指定的某个目的地。
到这里,已经基本掌握了导航组件的使用,后续的章节会进行更加深入地介绍导航组件的使用。
在导航图中,导航目的地不局限于 Fragment
,其实还可以是 Activity
,DialogFragment
甚至是嵌套的导航图navigation
(即
内部再嵌套一个
)。详细的添加导航目的地的方法已经在 3.3.2 向导航图中添加目的地 详细说明了,这里就不在累赘。
<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/homeFragment">
<fragment
android:id="@+id/homeFragment"
android:name="com.owen.navigationdemo.HomeFragment"
android:label="HomeFragment"
tools:layout="@layout/fragment_home" >
<action
android:id="@+id/action_homeFragment_to_loginFragment"
app:destination="@+id/loginFragment"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right" />
<action
android:id="@+id/action_homeFragment_to_settingsActivity"
app:destination="@+id/settingsActivity"
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>
<dialog
android:id="@+id/tips"
....... />
<activity
android:id="@+id/settingsActivity"
android:name="com.owen.navigationdemo.SettingsActivity"
android:label="SettingsActivity"
tools:layout="@layout/activity_settings" />
navigation>
注意:
1. 如果目的地是Activity
类型,转场动画必须结合Activity
处理,仅仅在导航连接目的地中声明动画,弹出动画将达不到预期的效果(参考:将弹出动画应用于 Activity 目的地过度);
2. 如果目的地是Activity
类型,实际上就是已经离开了当前的导航组件范围;
3. 如果使用声明导航目的地,必须是DialogFragment类型(包括其子类)。
所谓的嵌套导航图,就是在导航图内再嵌入一个导航图,外部的称为父导航图,内部的叫子导航图。嵌套的导航图封装着自己的目的地,且必须标识起始目的地,父导航图访问子导航图只能通过子导航图的起始目的地(不能直接访问子导航图中的目的地),因为子导航图拥有不一样的导航控制器(NavController)。使用嵌套导航图可以对导航目的地进行分类封装,防止错误的访问。
嵌套导航图有两种表现形式,一种是在导航图
标签内部嵌套一个
标签;另一种是使用 include
标签引入导航图资源文件。
注意事项:
1. 两种表现形式效果是一样的,如果导航图比较复杂,使用第二种会使得导航图资源显得更加简;
2. 父导航图中访问子导航图,不能直接访问子导航图中的目的地,只能通过子导航图ID访问子导航图的起始目的地.
标签内部嵌套一个
标签示例:
<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_login"
app:startDestination="@id/homeFragment">
<navigation android:id="@+id/Settings"
app:startDestination="@id/settingsFragment">
navigation>
navigation>
include
标签引入导航图资源文件示例:
nav_graph_settings.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"
android:id="@+id/Settings"
app:startDestination="@+id/settingsFragment">
navigation>
include
引入导航图资源
<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_login"
app:startDestination="@id/homeFragment">
<include app:graph="@navigation/nav_graph_settings" />
navigation>
操作就是目的地之间的跳转(
),全局操作就是指在导航图内所有目的地都能执行的操作。通常我们在目的地标签内部声明操作(
),但是全局操作是在导航图内声明(
标签下)。全局操作的使用跟普通的操作一样,不同的是他可以在当前导航图下所有的目的地内都可以使用。
注意事项:
1. 全局操作只能在同一导航图内的目的地中调用,不可在所声明的导航图外部使用,即使是子导航图中的目的地也不允许;
2. 全局操作的目的地必须是当前导航图下的目的地或者子导航图入口(子导航图中的目的地也是不允许的)
示例:
<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/Settings"
app:startDestination="@+id/settingsFragment">
<action
android:id="@+id/action_to_settings_more"
app:destination="@+id/commonFragment"
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
android:id="@+id/commonFragment"
android:label="CommonFragment"
android:name="com.owen.navigationdemo.CommonFragment"
tools:layout="@layout/fragment_common" />
navigation>
导航支持您通过定义目的地参数将数据附加到导航操作,在不同目的地之间实现数据传递。
提示:建议仅在目的地之间传递最少量的数据,因为在 Android 上用于保存所有状态的总空间是有限的,如果需要传输大量数据,可以考虑其他替代方案。
在导航图的操作中可以定义参数,在操作中定义的参数,有几个属性:
android:name
: 参数名android:defaultValue
: 参数默认值app:argType
: 参数类型app:nullable
: 是否可为空示例:
<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_login"
app:startDestination="@id/homeFragment">
<fragment
android:id="@+id/homeFragment"
android:name="com.owen.navigationdemo.HomeFragment"
android:label="HomeFragment"
tools:layout="@layout/fragment_home" >
<action
android:id="@+id/action_homeFragment_to_loginFragment"
app:destination="@+id/loginFragment"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right" >
action>
fragment>
<fragment
android:id="@+id/loginFragment"
android:name="com.owen.navigationdemo.LoginFragment"
android:label="LoginFragment"
tools:layout="@layout/fragment_login">
<action
android:id="@+id/action_loginFragment_to_registerFragment"
app:destination="@+id/registerFragment"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right"/>
<argument
android:name="type"
app:argType="integer"
android:defaultValue="0"
app:nullable="false"/>
<argument
android:name="uname"
app:argType="string"
android:defaultValue="@null"
app:nullable="false"/>
fragment>
navigation>
导航库支持的参数类型包括:
类型 | app:argType 语法 | 是否支持默认值 | 是否支持 null 值 |
---|---|---|---|
整数 | app:argType=“integer” | 是 | 否 |
浮点数 | app:argType=“float” | 是 | 否 |
长整数 | app:argType=“long” | 是 - 默认值必须始终以“L”后缀结尾(例如“123L”)。 | 否 |
布尔值 | app:argType=“boolean” | 是 -“true”或“false” | 否 |
字符串 | app:argType=“string” | 是 | 是 |
资源引用 | app:argType=“reference” | 是 - 默认值必须为“@resourceType/resourceName”格式(例如,“@style/myCustomStyle”)或“0” | 否 |
自定义 Parcelable | app:argType="",其中 是 Parcelable 的完全限定类名称 | 支持默认值“@null”。不支持其他默认值。 | 是 |
自定义 Serializable | app:argType="",其中 是 Serializable 的完全限定类名称 | 支持默认值“@null”。不支持其他默认值。 | 是 |
自定义 Enum | app:argType="",其中 是 Enum 的完全限定名称 | 是 - 默认值必须与非限定名称匹配(例如,“SUCCESS”匹配 MyEnum.SUCCESS)。 | 否 |
注意:
1. 如果参数传递的是Parcelable
、Serializable
和Enum
时,注意所传递的参数类型的类在代码混淆时需要做排除处理;
2.可以在
和
标签内声,如果需要通过深层链接传参,务必配置在
标签内声明(更多信息参考:创建隐式深层链接)。
在导航时,也可以实现在目的地之间传递参数,只需要调用带有参数传递的 NaviCotroller.navigate()
接口即可。
示例:
val navController = findNavController()
navController.navigate(R.id.action_homeFragment_to_loginFragment, Bundle().also {
it.putInt("type", 2)
})
注意事项:
1. 在导航图的操作中定义的参数,参数值是固定的,但是这个值可以被导航时传递的参数覆盖;
2. 导航的参数传递是单向的,无法实现往回传递,如果需要往回传递参数,可以通过目的地所有者Activity
进行。
导航支持在目的地之间添加动画,以提高用户体验。导航动画在定义操作是添加,动画包括个类型:
app:enterAnim
: 进入目的地的动画(新的目的地进入的动画)app:exitAnim
: 退出目的地的动画(新的目的地进入,旧目的地退出的动画)app:popEnterAnim
: 通过弹出操作进入的目的地的动画(弹出操作时,进入的目的地进入的动画)app:popExitAnim
: 通过弹出操作退出的目的地的动画(弹出操作时,退出的目的地退出的动画)示例:
<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_login"
app:startDestination="@id/homeFragment">
<fragment
android:id="@+id/homeFragment"
android:name="com.owen.navigationdemo.HomeFragment"
android:label="HomeFragment"
tools:layout="@layout/fragment_home" >
<action
android:id="@+id/action_homeFragment_to_loginFragment"
app:destination="@+id/loginFragment"
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>
navigation>
当导航的目的地是 Activity
类型时,在操作中添加的过度动画,会出现进入(打开 Activity
)时动画正常,但是返回时(从 Activity
中返回)的动画却失效了。针对目的地是 Activity
类型时,需要对弹出动画做特殊处理,您需要重写 Activity
的 finish()
方法,在内部调用 ActivityNavigator.applyPopAnimationsToPendingTransition(Activity)
接口即可。
示例:
override fun finish() {
super.finish()
ActivityNavigator.applyPopAnimationsToPendingTransition(this)
}
在 Android 中,深层链接是指将用户直接转到应用内特定目的地的链接。借助导航组件,您可以创建两种不同类型的深层链接:显式深层链接 和 隐式深层链接。
显式深层链接是深层链接的一个实例,该实例使用 PendingIntent 将用户转到应用内的特定位置(例如,可以在通知、应用快捷方式或应用微件中显示显式深层链接)。
当用户通过显式深层链接打开应用时,任务返回堆栈会被清除,并被替换为相应的深层链接目的地。当嵌套图表时,每个嵌套级别的起始目的地(即层次结构中每个
元素的起始目的地)也会添加到相应堆栈中。也就是说,当用户从深层链接目的地按下返回按钮时,就像从应用入口一步步进入到指定目的地的返回一样的效果。
创建显式深层链接,可以使用 NavDeepLinkBuilder 类构建 PendingIntent,如下例所示:
val pendingIntent = NavDeepLinkBuilder(requireContext())
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.accountSettingFragment)
.createPendingIntent()
如果已有 NavController,则还可以通过 NavController.createDeepLink() API 创建深层链接,如下所示:
val pendingIntent = findNavController()
.createDeepLink()
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.accountSettingFragment)
.createPendingIntent()
注意事项:
1. 第一种创建显式深层链接的方式中,如果提供的上下文不是Activity
,构造函数会使用 PackageManager.getLaunchIntentForPackage() 作为默认Activity
来启动(如果有);
2. 显式深层链接生成的对象是PendingIntent
,适合的场景有通知、快捷方式启动、桌面小部件等。
显式深层链接的使用示例(在通知中使用):
private fun showNotification(context: Context) {
var notificationManager:NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notificationManager = context.getSystemService<NotificationManager>(NotificationManager::class.java)
if (null != notificationManager) {
val channel = NotificationChannel("default", "default", NotificationManager.IMPORTANCE_DEFAULT
)
notificationManager.createNotificationChannel(channel)
}
} else {
notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
}
val pendingIntent = findNavController().createDeepLink()
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.accountSettingFragment)
.createPendingIntent()
val builder: NotificationCompat.Builder = NotificationCompat.Builder(context, "default")
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("测试深层链接")
.setContentText("测试显示深层链接打开应用")
.setContentIntent(pendingIntent) // .setVibrate(new long[] { 1000, 1000, 1000, 1000, 1000 })
.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
.setAutoCancel(true)
notificationManager.notify(1, builder.build())
}
隐式深层链接是指应用中特定目的地的 URI。调用 URI(例如用户点击某个链接)时,Android 可以将应用打开并自动导航到相应的目的地。
当用户触发隐式深层链接时,返回堆栈的状态取决于是否使用 Intent.FLAG_ACTIVITY_NEW_TASK
标记启动隐式 Intent
:
元素的起始目的地)也会添加到相应堆栈中。也就是说,当用户从深层链接目的地按下返回按钮时,就像从应用入口一步步进入到指定目的地的返回一样的效果。创建隐式深层链接请安一下步骤:
在导航图中添加隐式深层链接声明,只需要在导航图内目的地中添加
标签,配置相关属性:
<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_login"
app:startDestination="@id/homeFragment">
<fragment
android:id="@+id/homeFragment"
android:name="com.owen.navigationdemo.HomeFragment"
android:label="HomeFragment"
tools:layout="@layout/fragment_home" >
<action
android:id="@+id/action_homeFragment_to_loginFragment"
app:destination="@+id/loginFragment"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right" >
action>
<action
android:id="@+id/action_home_to_settings"
app:destination="@+id/Settings"
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>
<fragment
android:id="@+id/loginFragment"
android:name="com.owen.navigationdemo.LoginFragment"
android:label="LoginFragment"
tools:layout="@layout/fragment_login">
<action
android:id="@+id/action_loginFragment_to_registerFragment"
app:destination="@+id/registerFragment"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right"/>
<argument
android:name="type"
app:argType="integer"
android:defaultValue="0"
app:nullable="false"/>
<argument
android:name="uname"
app:argType="string"
android:defaultValue="@null"
app:nullable="false"/>
<deepLink
android:id="@+id/settingsDeepLink"
app:uri="https://www.owen.com"
android:autoVerify="false"/>
fragment>
navigation>
标签属性说明:
android:id
: 深层链接IDapp:uri
: 深层链接Uriandroid:autoVerify
: 要求 Google 验证您是相应 URI 的所有者(可选),API 23 开始有效。创建隐式深层链接需要注意的几点:
www.owen.com
,会同时和 http://www.owen.com
与 https://www.owen.com
匹配https://www.owen.com/users/{id}
与 https://www.owen.com/users/4
匹配。导航件通过将占位符名称与深层链接所指向的目的地中已定义的参数相匹配,并尝试将占位符值解析为相应的类型。如果目的地中没有定义具有相同名称的参数,则使用默认的 String
类型传参数。.*
通配符匹配 0 个或多个字符。null
的参数,则深层链接必须包含该参数且不能为空,否则打开应用会有异常。 声明了隐式深层链接,接下来必须启用隐式深层链接,在应用的 AndroidManifest.xml
文件中,在导航图所关联的
声明中添加
元素,如下例所示:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.owen.navigationdemo">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
intent-filter>
<nav-graph android:value="@navigation/nav_graph" />
activity>
<activity android:name=".UploadAvatarActivity" />
application>
manifest>
构建项目时,导航件会将
元素进行转换生成的
元素,以匹配导航图中的所有深层链接。
注意事项:
1. 启用隐式深层链接必须在导航图关联的Activity
声明中进行;
2.元素在 Android Studio 3.0 或 3.1 中不支持,使用这些版本时,必须改为手动添加
intent-filter
元素。
隐式深层链接是URI,可以编写一个含有深层链接跳转的 html 文件放到存储中,使用浏览器访问该 html 文件,如下示例:
<head>
<meta charset="UTF-8" />
<meta id="viewport" name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,minimal-ui">
head>
<html>
<input type="button" value="点击我打开Deeplink" onclick="javascrtpt:window.location.href='https://www.owen.com/'">
html>
将以上文件存储到手机存储中,在浏览器的地址栏中输入 file:///
(
为 html 文件在存储中的路径,例如:file:///sdcard/test_deeplink.html
),访问html之后(如下图),点击按钮就可以打开应用并进入到相应的目的地。
注意事项:
1. 如果深层链接中使用的协议头在其他应用中也声明了,打开深层链接系统时可能会弹出应用选择列表,这个问题可以在定义深层链接时,通过定义应用独有的协议头规避;
2. 如果导航组件自动生成的intent-filter
无法正确进入到目的地,请确认深层链接是否跟自动生成的intent-filter
一致,如果不一致,可以修改深层链接或者采用手动添加intent-filter
(这个笔者遇到了,https://www.owen.com
的深层链接,生成的intent-filter
中包含android:path="/"
导致无法正确访问到指定目的地,修改深层链接为https://www.owen.com/
解决了)。
手动添加 intent-filter
元素示例:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.owen.navigationdemo">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https"
android:host="www.owen.com"
android:path="/" />
intent-filter>
activity>
<activity android:name=".UploadAvatarActivity" />
application>
manifest>
导航组件有着非常大的优势,不但使用轻量级的 Fragment
,还能使用深层链接打开应用时,能自动构建返回栈(如果是隐式深层链接,启动务必是 Intent.FLAG_ACTIVITY_NEW_TASK
)。大家可以在自己的项目中尝试使用导航组件,享受导航组件带来的便利。