文章目录
一、前言二、导航简介2.1 导航组件的组成2.2 导航的原则2.2.1 固定的起始目的地2.2.2 导航状态表现为目的地堆栈2.2.3 在应用的任务中向上按钮和返回按钮行为相同2.2.4 向上按钮不会退出应用2.2.5 深度链接可以模拟手动导航
三、使用入门3.1 添加依赖3.2 创建导航图资源文件3.2.1 Android Studio中的Navigation Editor
3.3 向 Activity 添加导航宿主(NavHost)3.3.1 通过 XML 添加 NavHostFragment3.3.2 向导航图中添加目的地3.3.3 将某个目的地指定为起始目的地3.3.4 连接目的地3.3.5 导航到目的地3.3.5.1 检索导航控制器3.3.5.2 导航到目的地3.3.5.3 返回到指定目的地
四、进阶之路4.1 创建不同类型的导航目的地4.2 嵌套导航图4.2.1 在导航图 ``标签内部嵌套一个 `` 标签4.2.2 使用 `include` 标签引入导航图资源文件
4.3 全局操作4.4 在目的地之间传递数据4.4.1 定义参数4.4.2 在导航时传递参数
4.5 在目的地之间添加动画效果4.5.1 将弹出动画应用于 Activity 目的地过度
4.6 为目的地创建深层链接4.6.1 创建显式深层链接4.6.2 创建隐式深层链接第一步:在导航图中添加隐式深层链接声明第二步:启用导航图中的隐式深层链接第三步:测试隐式深层链接
编者语
一、前言
传统的应用开发,一般都是采用一个界面一个 Activity 的形式,但是大家都知道, Activity 在 Android 中是属于重量级的组件,从而导致程序资源消耗大,用户体验不佳。而导航组件 Navigation 采用的是 Fragment 轻量级的组件实现的,可以节省资源,提高用户体验。
二、导航简介
导航组件是 Android Jetpack 的一部分,主要用途是实现用户导航、进入和退出应用中不同内容片段的交互,不论是普通的按钮点击,还是应用栏、抽屉导航栏等复杂的模式,它都能轻松应对,当然,导航组件也有它既定的 导航原则 来确保一致且可预测的用户体验。
2.1 导航组件的组成
导航组件主要有三部分组成:
导航图(navGraph):这是包含所有导航相关信息的 XML 资源,这些信息包括应用内所有内容区域个体(称为目标,一般都是 Fragment),以及用户可以通过应用跳转的可能路径。导航宿主(NavHost):这是用来显示导航图中声明的目标的一个空白容器。导航组件包含一个默认的 NavHost 实现 (NavHostFragment),可用于显示 Fragment 目标。导航控制器(NavController):在 NavHost 中管理应用导航的目标,当用户在应用中进行操作时,导航控制器会控制目标的切换。
使用导航组件有各种优势,包括以下方面:
自动处理 Fragment 事务;在默认情况下,能够正确处理往返操作;支持动画和转场动画;支持导航界面模式(例如:抽屉式导航栏和底部导航栏)Safe Args 支持(一种可在导航和目标之间传递数据时提供类型安全的 Gradle 插件)ViewModel 支持可以使用Android Studio的 Navigation Editor 来编辑和查看导航图(必须使用AnroidStudio 3.3及以上版本)
2.2 导航的原则
在使用导航组件时,应当遵循一些原则,以提高用户体验。
注意:即使您未在项目中使用 Navigation 组件,您的应用也应遵循这些设计原则。
2.2.1 固定的起始目的地
顾名思义,您构建的应用必须有一个固定的起始目的地,这个起始目的地就是指当应用启动时蛋刀的第一个屏幕。起始目的地也是用户按返回按钮后,在回到启动器前看到的最后一个屏幕。
示例: 以上示例中,用户登录页面就是起始目的地,点击启动器图标打开应用,第一个启动的页面就是用户登录页面,在返回过程中,最后一个呈现的页面也是登录页面。
2.2.2 导航状态表现为目的地堆栈
在用户启动应用时,系统会启动一个新任务,并且显示起始目的地,这个起始目的地是应用导航的而基础。当用户在应用中进行导航时,栈顶的目标就是显示在屏幕上的,而栈内的所有目标都是历史记录。
导航组件会为你管理所有返回栈的顺序,当然你也可以自行管理,已达到某些目的。
2.2.3 在应用的任务中向上按钮和返回按钮行为相同
首先,说一下什么是向上按钮,向上按钮是指在应用中的返回上一级的按钮(一般是在用户导航栏中),返回按钮则是系统导航中的返回按钮。在应用的任务重,向上按钮和返回按钮的行为相同,都是将栈顶的目标移除,返回到上一个目标。
2.2.4 向上按钮不会退出应用
在应用的任务重,向上按钮可以返回到上一个目标,但是绝不会退出应用。
2.2.5 深度链接可以模拟手动导航
无论是通过深度链接至特定的目的地,还是手动导航到特定目的地,都可以使用向上按钮通过各个目的地导航回到起始目的地。当深度链接至特定的目的地时,会移除所有返回栈中的任务,并替换为深度链接的返回栈。值得注意的是,深度链接合成的返回栈是一个完整的返回栈,他跟手动导航至特定目的地具有相同的返回栈,这个是非常重要的,因为合成的返回栈必须是真实的。
三、使用入门
本文对应的Demo源码请访问Github:NavigationDemo
3.1 添加依赖
在应用模块目录下的 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"
}
3.2 创建导航图资源文件
导航是发生在各个目的地之间的,而这些目的地通过操作连接在一起。导航图是一种资源文件,它包含了所有的目的地和操作的声明。
创建导航图资源文件,可以按以下不走进行:
在项目程序模块下面的 res 目录下,右键-》“New”-》“Android Resource File”;在弹出的窗口中输入文件名;在“Resource type”中选择“Navigation”;点击确定创建资源文件。
新建的导航图资源文件是一个 XML 资源文件,以 navigation 为根节点,大致内容如下。
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/nav_graph">
如果是首次添加导航图资源,Android Studio 会在 res 目录内创建一个 navigation 资源目录,该目录包含您的导航图资源文件。当然,如果你已经够熟练,可以直接创建目录和文件的方式创建。
3.2.1 Android Studio中的Navigation Editor
Android Studio提供了强大的导航编辑器,在这里不但可以预览您所添加的目标,还可以修改导航图,可以通过拖动的方式或者直接编码修改底层 XML 的方式修改导航图。为了方便项目的维护和代码可读性,笔者更加建议使用修改底层 XML 的方式,或者结合修改底层 XML 的方式。
温馨提示:不同版本的Android Studio的界面操作有些不一样,不少从旧版升级到3.6之后,发现打开资源文件的时候,默认是 “Design” 模式(包括layout布局资源),一时间找不到北了,不知道如何切换成修改底层 XML 的模式。其实这是3.6 版本之后的小变动,在旧版本只有 “Code” 和 “Design” 两种模式,新版有 “Code”、“Split” 、“Design”三种模式,而且模式切换的位置也变了,旧版是在左下角,而新版是在右上角,而且是图标的形式(如下图)。
3.3 向 Activity 添加导航宿主(NavHost)
导航宿主是导航组件的核心部分之一,导航宿主是一个空容器,用来存放和处理目的地。导航宿主必须派生于 NavHost,NavHostFragment 是导航组件的默认导航宿主实现,负责处理 Fragment 目的地的交换。
注意:导航组件的设计理念是用于具有一个主 Activity 和多个 Fragment 目的地的应用,主 Activity 与导航图相关联,并且包含一个负责根据需要交换目的地的 NavHostFragment。如果您的应用需要在多个 Activity 上实现导航,就需要为每个 Activity 添加导航宿主,并在每个 Activity 关联其自己的导航图。
3.3.1 通过 XML 添加 NavHostFragment
在主 Activity 的布局文件中,添加
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"> 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" />
详细解说:
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 添加导航宿主,具体步骤如下:
打开 Activity 布局文件,切换到 “Design” 窗口;在 “Palette” 窗口选择 “Containers” ,然后找到 “NavHostFragment”(可直接搜索);将 “NavHostFragment” 拖向布局中;在弹出的窗口中选择导航图,然后确定;在 “Properties” 窗口设置相关属性。
3.3.2 向导航图中添加目的地
对于懒于编码的小伙伴可以使用 Navigation Editor 向导航中添加目的地,因为这些都是用户引导模式的,没什么可说,我这里主要讲一下手动添加目的地的步骤:
新建 Fragment 类和布局文件,并实现相关逻辑代码;在导航图 XML 中新增
示例:
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"> android:id="@+id/loginFragment" android:name="com.owen.navigationdemo.LoginFragment" android:label="LoginFragment" tools:layout="@layout/fragment_login"> 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"/> android:id="@+id/registerFragment" android:name="com.owen.navigationdemo.RegisterFragment" android:label="RegisterFragment" tools:layout="@layout/fragment_register">
目的地属性详解
Type:即标签名称,指示在源代码中,该目的地是作为 Fragment、Activity还是其他自定义类实现的anroid:label:这个属性指定目的地的名称android:id: 这个属性指定改目的地的ID,用于在代码中引用该目的地android:name:这个属性用来指定目的地所关联的类 除此之外,还可以通过tools:layout属性指定预览布局文件,这样就可以在导航图编辑器中看到对应的布局预览。
3.3.3 将某个目的地指定为起始目的地
导航的原则之一就是固定的起始目的地,指定起始目的地的方法有两种,一种是使用 Navigation Editor,在 “Design”窗口中,选中需要指定为起始目的地的目标,点击 “房子”图标(如下图)即可。另一种方法就是在 XML 源代码中,在
示例:通过 XML 代码指定起始目的地
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"> android:id="@+id/loginFragment" android:name="com.owen.navigationdemo.LoginFragment" android:label="LoginFragment" tools:layout="@layout/fragment_login"> 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"/> android:id="@+id/registerFragment" android:name="com.owen.navigationdemo.RegisterFragment" android:label="RegisterFragment" tools:layout="@layout/fragment_register">
3.3.4 连接目的地
目的地之间的逻辑连接也叫做操作,操作一般是将一个目的地连接到另一个目的地,当然,你也可以定义 全局操作 ,这类操作可以在任意位置跳转到指定的目的地,这个我们在后面会详细讲到。 您可以使用 Navigation Editor 连接两个目的地,直接拖动箭头即可,在这里就不多介绍这种方式,直接介绍通过修改 XML 源码的方式(其实使用 Navigation Editor 也会自动修改 XML 源码),具体步骤如下:
在
详细解说:
Type:即
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"> android:id="@+id/homeFragment" android:name="com.owen.navigationdemo.HomeFragment" android:label="HomeFragment" tools:layout="@layout/fragment_home" > 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" />
3.3.5 导航到目的地
完成了导航图的各种配置,那么就需要在代码中实现导航到目的地了。导航到目的地是使用 NavController 完成的,这是在导航宿主中管理导航的对象的,每个导航宿主都有自己的相应导航控制器(NavController)。导航到目的地的步骤如下:
检索导航控制器;导航到目的地。
3.3.5.1 检索导航控制器
检索导航宿主的导航控制器,可以通过以下方法:
Kotlin:
Fragment.findNavController()View.findNavController()Activity.findNavController(viewId: Int)
Java:
NavHostFragment.findNavController(Fragment)Navigation.findNavController(Activity, @IdRes int viewId)Navigation.findNavController(View)
说明: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
// Kotlin 扩展方法检索当前导航宿主的导航控制器
val navController = findNavController()
}
return view
}
}
3.3.5.2 导航到目的地
检索到导航控制器之后,使用导航控制器类的 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
// Kotlin 扩展方法检索当前导航宿主的导航控制器
val navController = findNavController()
navController.navigate(R.id.action_homeFragment_to_loginFragment)
}
return view
}
}
3.3.5.3 返回到指定目的地
返回到指定的目的地,是指返回到之前导航过的目的地,这些目的地必须是在任务栈内的,可以通过 NavController.popBackStack() 接口返回上一级,或者通过 NavController.popBackStack (int destinationId, boolean inclusive) 返回到指定的某个目的地。
到这里,已经基本掌握了导航组件的使用,后续的章节会进行更加深入地介绍导航组件的使用。
四、进阶之路
4.1 创建不同类型的导航目的地
在导航图中,导航目的地不局限于 Fragment,其实还可以是 Activity,DialogFragment 甚至是嵌套的导航图navigation(即
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"> android:id="@+id/homeFragment" android:name="com.owen.navigationdemo.HomeFragment" android:label="HomeFragment" tools:layout="@layout/fragment_home" > 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" /> 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" />
注意:1. 如果目的地是 Activity 类型,转场动画必须结合 Activity 处理,仅仅在导航连接目的地中声明动画,弹出动画将达不到预期的效果(参考:将弹出动画应用于 Activity 目的地过度);2. 如果目的地是 Activity 类型,实际上就是已经离开了当前的导航组件范围;3. 如果使用
4.2 嵌套导航图
所谓的嵌套导航图,就是在导航图内再嵌入一个导航图,外部的称为父导航图,内部的叫子导航图。嵌套的导航图封装着自己的目的地,且必须标识起始目的地,父导航图访问子导航图只能通过子导航图的起始目的地(不能直接访问子导航图中的目的地),因为子导航图拥有不一样的导航控制器(NavController)。使用嵌套导航图可以对导航目的地进行分类封装,防止错误的访问。
嵌套导航图有两种表现形式,一种是在导航图
注意事项:1. 两种表现形式效果是一样的,如果导航图比较复杂,使用第二种会使得导航图资源显得更加简;2. 父导航图中访问子导航图,不能直接访问子导航图中的目的地,只能通过子导航图ID访问子导航图的起始目的地.
4.2.1 在导航图
示例:
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"> app:startDestination="@id/settingsFragment">
4.2.2 使用 include 标签引入导航图资源文件
示例:
定义一个导航图资源文件(nav_graph_settings.xml)
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/Settings" app:startDestination="@+id/settingsFragment">
在主导航图中使用 include 引入导航图资源
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">
4.3 全局操作
操作就是目的地之间的跳转(
注意事项:1. 全局操作只能在同一导航图内的目的地中调用,不可在所声明的导航图外部使用,即使是子导航图中的目的地也不允许;2. 全局操作的目的地必须是当前导航图下的目的地或者子导航图入口(子导航图中的目的地也是不允许的)
示例:
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/Settings" app:startDestination="@+id/settingsFragment"> 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" /> android:id="@+id/commonFragment" android:label="CommonFragment" android:name="com.owen.navigationdemo.CommonFragment" tools:layout="@layout/fragment_common" />
4.4 在目的地之间传递数据
导航支持您通过定义目的地参数将数据附加到导航操作,在不同目的地之间实现数据传递。
提示:建议仅在目的地之间传递最少量的数据,因为在 Android 上用于保存所有状态的总空间是有限的,如果需要传输大量数据,可以考虑其他替代方案。
4.4.1 定义参数
在导航图的操作中可以定义参数,在操作中定义的参数,有几个属性:
android:name: 参数名android:defaultValue: 参数默认值app:argType: 参数类型app:nullable: 是否可为空
示例:
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"> android:id="@+id/homeFragment" android:name="com.owen.navigationdemo.HomeFragment" android:label="HomeFragment" tools:layout="@layout/fragment_home" > 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" > android:id="@+id/loginFragment" android:name="com.owen.navigationdemo.LoginFragment" android:label="LoginFragment" tools:layout="@layout/fragment_login"> 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"/> android:name="type" app:argType="integer" android:defaultValue="0" app:nullable="false"/> android:name="uname" app:argType="string" android:defaultValue="@null" app:nullable="false"/>
导航库支持的参数类型包括:
类型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”否自定义 Parcelableapp:argType="",其中 是 Parcelable 的完全限定类名称支持默认值“@null”。不支持其他默认值。是自定义 Serializableapp:argType="",其中 是 Serializable 的完全限定类名称支持默认值“@null”。不支持其他默认值。是自定义 Enumapp:argType="",其中 是 Enum 的完全限定名称是 - 默认值必须与非限定名称匹配(例如,“SUCCESS”匹配 MyEnum.SUCCESS)。否
注意:1. 如果参数传递的是 Parcelable、Serializable 和 Enum时,注意所传递的参数类型的类在代码混淆时需要做排除处理;2.
4.4.2 在导航时传递参数
在导航时,也可以实现在目的地之间传递参数,只需要调用带有参数传递的 NaviCotroller.navigate() 接口即可。
示例:
val navController = findNavController()
navController.navigate(R.id.action_homeFragment_to_loginFragment, Bundle().also {
it.putInt("type", 2)
})
注意事项:1. 在导航图的操作中定义的参数,参数值是固定的,但是这个值可以被导航时传递的参数覆盖;2. 导航的参数传递是单向的,无法实现往回传递,如果需要往回传递参数,可以通过目的地所有者 Activity 进行。
4.5 在目的地之间添加动画效果
导航支持在目的地之间添加动画,以提高用户体验。导航动画在定义操作是添加,动画包括个类型:
app:enterAnim: 进入目的地的动画(新的目的地进入的动画)app:exitAnim: 退出目的地的动画(新的目的地进入,旧目的地退出的动画)app:popEnterAnim: 通过弹出操作进入的目的地的动画(弹出操作时,进入的目的地进入的动画)app:popExitAnim: 通过弹出操作退出的目的地的动画(弹出操作时,退出的目的地退出的动画)
示例:
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"> android:id="@+id/homeFragment" android:name="com.owen.navigationdemo.HomeFragment" android:label="HomeFragment" tools:layout="@layout/fragment_home" > 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" />
4.5.1 将弹出动画应用于 Activity 目的地过度
当导航的目的地是 Activity 类型时,在操作中添加的过度动画,会出现进入(打开 Activity)时动画正常,但是返回时(从 Activity 中返回)的动画却失效了。针对目的地是 Activity 类型时,需要对弹出动画做特殊处理,您需要重写 Activity 的 finish() 方法,在内部调用 ActivityNavigator.applyPopAnimationsToPendingTransition(Activity) 接口即可。 示例:
override fun finish() {
super.finish()
ActivityNavigator.applyPopAnimationsToPendingTransition(this)
}
4.6 为目的地创建深层链接
在 Android 中,深层链接是指将用户直接转到应用内特定目的地的链接。借助导航组件,您可以创建两种不同类型的深层链接:显式深层链接 和 隐式深层链接。
4.6.1 创建显式深层链接
显式深层链接是深层链接的一个实例,该实例使用 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
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())
}
4.6.2 创建隐式深层链接
隐式深层链接是指应用中特定目的地的 URI。调用 URI(例如用户点击某个链接)时,Android 可以将应用打开并自动导航到相应的目的地。
当用户触发隐式深层链接时,返回堆栈的状态取决于是否使用 Intent.FLAG_ACTIVITY_NEW_TASK 标记启动隐式 Intent:
如果该标记已设置,则任务返回堆栈会被清除,并被替换为相应的深层链接指定的目的地。就像显式深层链接,当嵌套图表时,每个嵌套级别的起始目的地(即层次结构中每个
创建隐式深层链接请安一下步骤:
第一步:在导航图中添加隐式深层链接声明
在导航图中添加隐式深层链接声明,只需要在导航图内目的地中添加
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"> android:id="@+id/homeFragment" android:name="com.owen.navigationdemo.HomeFragment" android:label="HomeFragment" tools:layout="@layout/fragment_home" > 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" > 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" /> android:id="@+id/loginFragment" android:name="com.owen.navigationdemo.LoginFragment" android:label="LoginFragment" tools:layout="@layout/fragment_login"> 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"/> android:name="type" app:argType="integer" android:defaultValue="0" app:nullable="false"/> android:name="uname" app:argType="string" android:defaultValue="@null" app:nullable="false"/> android:id="@+id/settingsDeepLink" app:uri="https://www.owen.com" android:autoVerify="false"/>
android:id: 深层链接IDapp:uri: 深层链接Uriandroid:autoVerify: 要求 Google 验证您是相应 URI 的所有者(可选),API 23 开始有效。
创建隐式深层链接需要注意的几点:
没有协议的 URI 会假定为同时支持 http 和 https。例如:www.owen.com,会同时和 http://www.owen.com 与 https://www.owen.com 匹配深层链接的后缀中可以包含形式为 {placeholder_name} 的占位符,用来匹配一个或多个字符。例如,https://www.owen.com/users/{id} 与 https://www.owen.com/users/4 匹配。导航件通过将占位符名称与深层链接所指向的目的地中已定义的参数相匹配,并尝试将占位符值解析为相应的类型。如果目的地中没有定义具有相同名称的参数,则使用默认的 String 类型传参数。在深层链接的后缀中,可以使用 .* 通配符匹配 0 个或多个字符。如果目的地参数列表中定义了不能为 null 的参数,则深层链接必须包含该参数且不能为空,否则打开应用会有异常。
第二步:启用导航图中的隐式深层链接
声明了隐式深层链接,接下来必须启用隐式深层链接,在应用的 AndroidManifest.xml文件中,在导航图所关联的
package="com.owen.navigationdemo"> 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">
构建项目时,导航件会将
注意事项:1. 启用隐式深层链接必须在导航图关联的 Activity 声明中进行;2.
第三步:测试隐式深层链接
隐式深层链接是URI,可以编写一个含有深层链接跳转的 html 文件放到存储中,使用浏览器访问该 html 文件,如下示例:
将以上文件存储到手机存储中,在浏览器的地址栏中输入 file:///
注意事项:1. 如果深层链接中使用的协议头在其他应用中也声明了,打开深层链接系统时可能会弹出应用选择列表,这个问题可以在定义深层链接时,通过定义应用独有的协议头规避;2. 如果导航组件自动生成的 intent-filter 无法正确进入到目的地,请确认深层链接是否跟自动生成的 intent-filter 一致,如果不一致,可以修改深层链接或者采用手动添加 intent-filter(这个笔者遇到了,https://www.owen.com 的深层链接,生成的 intent-filter 中包含 android:path="/" 导致无法正确访问到指定目的地,修改深层链接为 https://www.owen.com/ 解决了)。
手动添加 intent-filter 元素示例:
package="com.owen.navigationdemo"> 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">
android:host="www.owen.com" android:path="/" />
编者语
导航组件有着非常大的优势,不但使用轻量级的 Fragment,还能使用深层链接打开应用时,能自动构建返回栈(如果是隐式深层链接,启动务必是 Intent.FLAG_ACTIVITY_NEW_TASK)。大家可以在自己的项目中尝试使用导航组件,享受导航组件带来的便利。