拥抱JetPack之Navigation

目录

前言

一、Navigation简介

1.1、什么是Navigation

1.2、添加依赖

二、上手体验

2.1、创建FragmentContainerView

2.2、创建navigation/nav_graph导航图

三、常用API介绍

3.1、navigate

3.2、popBackStack

3.3、navigateUp

3.4、clearBackStack

前言

本篇继续来学习JetPack组件库的相关用法,在上一篇《拥抱JetPack之印象篇》中,相信大家已经对JetPack有了一个基本的认识,那接下来就来具体说一说JetPack组件库中具体组件的使用,今天我们要说的是Navigation。

本文参考文档:https://developer.android.google.cn/guide/navigation?hl=zh-cn

一、Navigation简介

1.1、什么是Navigation

一句话总结:它是一个类似于ARouter的导航路由框架,它具有以下特点:

  • 支持 Activity、Fragment、Dialog三种组件类型的路由行为
  • 支持deepLink 能力,即端外唤起
  • ViewModel支持,可以将ViewModel的生命周期限定为导航图,能够方便的在多个目标页之间共享与界面相关的数据
  • Safe Args 可以在目标之间传递数据时提供类型安全的Gradle插件

1.2、添加依赖

在项目工程的gradle目录下的libs.versions.toml文件中添加:

[versions]
navigation = "2.9.3"
[libraries]
navigation-fragment = { group = "androidx.navigation", name = "navigation-fragment", version.ref = "navigation" }
navigation-ui = { group = "androidx.navigation", name = "navigation-ui", version.ref = "navigation" }

在app module的build.gradle的dependencies节点中引入依赖:

implementation libs.navigation.fragment
implementation libs.navigation.ui

二、上手体验

先给大家贴一张图,这个是官网指南里的关于Navigation的几个主要的概念,我们学习Navigation如何使用,实际上也就是围绕这几个概念来学习的:

2.1、创建FragmentContainerView

简单来说:它是一个视图容器,是一个自定义View,继承自FrameLayout,与ViewGroup不同,只接受Fragment作为子View。这里为了让Fragment的视图能够显示到容器中,还需要做以下操作:

①、为它指定name属性为NavHostFragment;

②、设置它的app:defaultNavHost属性值为true,指定该值后可以拦截Activity的onBackPressd()事件,进而可以正确处理Fragment的出栈操作;

③、指定app:navGraph属性,它的值是一个资源文件,这个资源文件就是上图中介绍到的导航图,关于这个资源文件的创建和使用在下一个章节会介绍到。

简单示例代码如下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/nav_graph" />

</androidx.constraintlayout.widget.ConstraintLayout>

2.2、创建navigation/nav_graph导航图

在res目录下创建一个navigation文件夹,然后在该文件夹下新建一个Navigation Resource File,这里可以命名为nav_graph.xml。它本质上是一个导航图,描述了整个应用所有页面的路由信息,在使用时需要注意以下几点:

①、根节点必须是navigation;

②、使用app:startDestination指定开始节点;

③、使用<fragment/>、<activity/>、<dialog/>等创建路由节点;

④、使用<action/>指定某个节点的路由动作,它有两个参数:id和destination;

⑤、使用<deepLink/>时需要指定参数app:uri,这个uri其实就是scheme host path,在三方应用中可以通过该uri来拉起对应的页面,跟使用隐式意图拉起Activity的效果类似;

⑥、使用<argument/>进行参数传递,它有两个必要的属性:name参数名称&argType参数类型,关于具体支持的参数类型可以参考:支持的参数类型

简单示例代码如下:

<?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"
    app:startDestination="@id/fragmentA">
    <fragment
        android:id="@+id/fragmentA"
        android:name="com.jarchie.connotativejokes.FragmentA">
        <action
            android:id="@+id/navigate_to_fragmentB"
            app:destination="@+id/fragmentB" />
    </fragment>
    <fragment
        android:id="@+id/fragmentB"
        android:name="com.jarchie.connotativejokes.FragmentB">
        <action
            android:id="@+id/navigate_to_fragmentC"
            app:destination="@+id/fragmentC" />
    </fragment>
    <fragment
        android:id="@+id/fragmentC"
        android:name="com.jarchie.connotativejokes.FragmentC">
        <action
            android:id="@+id/navigate_to_fragmentD"
            app:destination="@+id/fragmentD" />
    </fragment>
    <fragment
        android:id="@+id/fragmentD"
        android:name="com.jarchie.connotativejokes.FragmentD"/>
</navigation>

参考文档:设计导航图 & 使用导航操作示例

三、常用API介绍

3.1、navigate

  • navigate()方法:
    • 参数:resId:Int(目标页ID)、args:Bundle(目标页面入参)、options:NavOptions(启动行为)
    • 功能:跳转到指定resId目标页面,通过NavOptions可以控制启动行为,比如singleTop启动、弹出栈启动、页面转场动画等

方式一:navigate(目标页id)-普通启动方式

val navController = findNavController()
        aBinding.navigateToFragmentB.setOnClickListener {
            // 对于Fragment类型的路由节点,在navigate跳转的时候,使用的是FragmentTransaction.replace()方法
            navController.navigate(R.id.fragmentB)
        }

方式二:setPopUpTo()-弹出栈启动方式

 cBinding.navigateToFragmentD.setOnClickListener {
            navController.navigate(R.id.fragmentD, null,
                NavOptions.Builder()
                    .setPopUpTo( destinationId = R.id.fragmentB, inclusive = true, saveState = true)
                    .build()
            )
        }

setPopUpTo()简介:

  • 第一个参数是一个页面ID,在跳转时会把路由的结果页和这里指定ID的页面中间所有的页面弹出栈;
  • 第二个参数inclusive默认值false,它的状态决定了是否包含这里指定的这个ID所对应的页面;
  • 第三个参数saveState默认值false,它的状态决定了被弹出栈的这些页面的状态是否需要保存,当这些页面在下一次被启动时,可以通过onCreate(savedInstanceState:Bundle?)方法中的savedInstanceState这个参数进行数据的还原操作,启动新页面并且还原数据时需要调用NavOptions.Builder().setRestoreState(true).build()方法。

方式三:SingleTop-栈顶启动方式

dBinding.navigateToFragmentD.setOnClickListener {
            navController.navigate(
                R.id.fragmentD, null,
                NavOptions.Builder().setLaunchSingleTop(true).build()
            )
        }

3.2、popBackStack

  • popBackStack方法:
    • 参数1(resId): 需要出栈的目标页ID
    • 参数2(inclusive): 默认false,表示是否包含目标页一起出栈
    • 参数3(saveState): 默认false,表示是否保存页面数据状态
    • 作用:回退到resId目标页面,当inclusive=true时连同目标页一起弹出栈;saveState=true时,目标页与当前页之间的所有页面的数据会被保存,用以后续在重建时恢复
navController.popBackStack(
                R.id.fragmentA, inclusive = false, saveState = true
            )

3.3、navigateUp

  • 基本功能:与popBackStack方法类似,都是用于回退页面栈的操作
  • 特殊场景:如果当前页面是页面栈的第一个页面并且导航视图的启动页已不存在时,会重启当前Activity
  • 参数特性:该方法不需要任何参数,依赖当前页面栈状态,自动判断执行逻辑
navController.navigateUp()

3.4、clearBackStack

  • 参数:resId,需要回退到的目标页Id
  • 功能:清除调用popBackStack保存过的页面状态的数据,同时回退到resId页面。若之前未调用过popBackStack,则该API无效。
// D页面
dBinding.navigateToFragmentA.setOnClickListener {
            navController.popBackStack(
                R.id.fragmentA, inclusive = false, saveState = true
            )
        }
// A页面
aBinding.navigateToFragmentB.setOnClickListener {
            navController.clearBackStack(R.id.fragmentB)
            navController.navigate(
                R.id.fragmentB,
                null,
                NavOptions.Builder().setRestoreState(true).build()
            )
        }

首先在上面的导航图nav_graph.xml文件中的FragmentD中添加一个deepLink节点:

<fragment
        android:id="@+id/fragmentD"
        android:name="com.jarchie.connotativejokes.FragmentD">
        <deepLink
            app:uri="https://com.jarchie.connotativejokes/admin" />
    </fragment>

然后在FragmentA中按钮的点击事件里通过:

navController.navigate("https://com.jarchie.connotativejokes/admin".toUri())

或者

navController.navigate(
                NavDeepLinkRequest.Builder
                    .fromUri("https://com.jarchie.connotativejokes/admin".toUri())
                    .build()
            )

都可以跳转到FragmentD页面中。

Navigation特点简单总结:

  • 需在nav_graph.xml提前配置所有页面节点, 不易维护
  • 需在nav_graph.xml配置固定的startDestination启动节点, 不够灵活
  • Fragment类型的路由导航使用的replace, 会使得页面频繁重建,体验不好
  • Fragment singleTop启动模式, 即便目标页在栈顶, 依然会销毁后再重建, 体验不好

OK,相信大家已经对Navigation有个初步的印象了,那今天的内容就到这里吧,再会!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值