一文详解Jetpack Android新一代导航管理Navigation

目录
  • 前言
  • 创建导航视图
  • 添加NavHost
  • 导航
  • findNavController
  • ToolBar
  • 总结

前言

不知道小伙伴们是否注意到,用AS创建一个默认的新项目后,MainActivity已经有了很大的不同,最大的区别就是新增加了两个Fragment,同时我们注意到这两个Fragment之间跳转的时候并没有使用之前FragmentTransaction这种形式,而是使用了NavController和NavHostFragment,这就是新一代导航管理————Navigation。

项目中依赖Navigation:

implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'

创建导航视图

新建一个Android Resource File,类型选择Navigation即可,输入名称后我们就创建了一个导航视图。

在导航试图中,我们可以通过添加activity/fragment等标签手动添加页面,也支持在Design页面中通过界面添加,如下:

注意:这样添加后手动修改一下label。如果我们将Navigation与ToolBar连接,会在标题栏这个label。

示例中添加了两个页面,添加后代码如下:

<?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"
    xmlns:tools="http://schemas.android.com/tools">
    <fragment
        android:id="@+id/FirstFragment"
        android:name="com.xxx.xxx.FirstFragment"
        android:label="@string/first_fragment_label"
        tools:layout="@layout/fragment_first">
    </fragment>
    <fragment
        android:id="@+id/SecondFragment"
        android:name="com.xxx.xxx.SecondFragment"
        android:label="@string/second_fragment_label"
        tools:layout="@layout/fragment_second">
    </fragment>
</navigation>

除了添加Fragment和Activity,Google还提供了一个占位符placeholder,添加加完代码如下:

<fragment android:id="@+id/placeholder" />

用于暂时占位以便后面可以替换为Fragment和Activity

添加完页面后,我们还需要添加页面之间的导航,可以手动添加action标签,当然也可以通过拖拽来实现,如下:

这样我们就添加了一个从FirstFragment导航到SecondFragment的动作,我们再添加一个逆向的动作,最终的代码如下:

<?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"
    xmlns:tools="http://schemas.android.com/tools">
    <fragment
        android:id="@+id/FirstFragment"
        android:name="com.xxx.xxx.FirstFragment"
        android:label="@string/first_fragment_label"
        tools:layout="@layout/fragment_first">
        <action
            android:id="@+id/action_FirstFragment_to_SecondFragment"
            app:destination="@id/SecondFragment" />
    </fragment>
    <fragment
        android:id="@+id/SecondFragment"
        android:name="com.xxx.xxx.SecondFragment"
        android:label="@string/second_fragment_label"
        tools:layout="@layout/fragment_second">
        <action
            android:id="@+id/action_SecondFragment_to_FirstFragment"
            app:destination="@id/FirstFragment" />
    </fragment>
</navigation>

注意占位符placeholder同样支持添加导航。

这样就实现了两个页面间的导航,最后还需要为这个navigation设置id和默认页面startDestination,如下:

<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/FirstFragment">

这样导航视图就创建完成了。可以看到Google力图通过可视化工具来简化开发工作,这对我们开发者来说非常有用,可以省去大量编写同质化代码的时间。

添加NavHost

下一步我们需要向Activity中添加导航宿主,导航宿主是一个空页面,必须实现NavHost接口,我们使用Navigation提供的默认NavHost————NavHostFragment即可。如下:

<fragment
    android:id="@+id/nav_host_fragment_content_main"
    android:name="androidx.navigation.fragment.NavHostFragment"
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:defaultNavHost="true"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:navGraph="@navigation/nav_graph" />

在Activity的视图中添加一个fragment标签,android:name设置为实现类,即NavHostFragment;app:navGraph设置为刚才新建的导航视图。

注意app:defaultNavHost="true",设置为true后表示将这个NavHostFragment设置为默认导航宿主,这样就会拦截系统的返回按钮事件。同一布局中如果有多个导航宿主(比如双窗口)则必须制定一个为默认的导航宿主。

这时候我们运行应用,就可以发现Activity中已经可以展示FirstFragment了。

导航

我们还需要为两个fragment添加按钮,是其点击跳转到另外一个页面,代码如下:

binding.buttonFirst.setOnClickListener {
    findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment)
}

示例中是FirstFragment中的一个按钮,点击时执行了id为action_FirstFragment_to_SecondFragment的动作,这个是我们之前在导航视图中配置好的,会导航到SecondFragment。

注意首先通过findNavController()来获取一个NavController对象,然后调用它的navigate函数即可,当然这个函数有多种重载,比如可以传递参数,如下:

public void navigate(@IdRes int resId, @Nullable Bundle args) {

这里不一一列举了,大家自行查看源码即可。

可以看到使用Navigation代码精简了很多,只需要一行代码执行一个函数即可。

findNavController

我们重点来看看findNavController(),它是一个扩展函数,如下:

fun Fragment.findNavController(): NavController =
        NavHostFragment.findNavController(this)

实际上是NavHostFragment的一个静态函数findNavController:

@NonNull
public static NavController findNavController(@NonNull Fragment fragment) {
    ...
    View view = fragment.getView();
    if (view != null) {
        return Navigation.findNavController(view);
    }
    // For DialogFragments, look at the dialog's decor view
    Dialog dialog = fragment instanceof DialogFragment
            ? ((DialogFragment) fragment).getDialog()
            : null;
    if (dialog != null && dialog.getWindow() != null) {
        return Navigation.findNavController(dialog.getWindow().getDecorView());
    }
    throw new IllegalStateException("Fragment " + fragment
            + " does not have a NavController set");
}

通过源码可以看到最终是执行了Navigation的findNavController函数,它的代码如下:

@NonNull
public static NavController findNavController(@NonNull View view) {
    NavController navController = findViewNavController(view);
    ...
    return navController;
}

这里是通过findViewNavController函数来获取NavController的,它的代码如下:

@Nullable
private static NavController findViewNavController(@NonNull View view) {
    while (view != null) {
        NavController controller = getViewNavController(view);
        if (controller != null) {
            return controller;
        }
        ViewParent parent = view.getParent();
        view = parent instanceof View ? (View) parent : null;
    }
    return null;
}

这里可以看到通过view来获取NavController,如果没有则向上层查找(父view)直到找到或到根结点。getViewNavController代码如下:

@Nullable
private static NavController getViewNavController(@NonNull View view) {
    Object tag = view.getTag(R.id.nav_controller_view_tag);
    NavController controller = null;
    if (tag instanceof WeakReference) {
        controller = ((WeakReference<NavController>) tag).get();
    } else if (tag instanceof NavController) {
        controller = (NavController) tag;
    }
    return controller;
}

看到这里获取view中key为R.id.nav_controller_view_tag的tag,这个tag就是NavController,那么这个tag又从哪来的?

其实就是上面我们提到导航宿主————NavHostFragment,在他的onViewCreated中可以看到如下代码:

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    if (!(view instanceof ViewGroup)) {
        throw new IllegalStateException("created host view " + view + " is not a ViewGroup");
    }
    Navigation.setViewNavController(view, mNavController);
    // When added programmatically, we need to set the NavController on the parent - i.e.,
    // the View that has the ID matching this NavHostFragment.
    if (view.getParent() != null) {
        mViewParent = (View) view.getParent();
        if (mViewParent.getId() == getId()) {
            Navigation.setViewNavController(mViewParent, mNavController);
        }
    }
}

这里的mNavController是在NavHostFragment的onCreate中创建出来的,是一个NavHostController对象,它继承NavController,所以就是NavController。

可以看到onViewCreated中调用了Navigation的setViewNavController函数,它的代码如下:

public static void setViewNavController(@NonNull View view,
        @Nullable NavController controller) {
    view.setTag(R.id.nav_controller_view_tag, controller);
}

这样就将NavController加入tag中了,通过findNavController()就可以得到这个NavController来执行导航了。

注意在onViewCreated中不仅为Fragment的View添加了tag,同时还为其父View也添加了,这样做的目的是在Activity中也可以获取到NavController,这点下面就会遇到。

ToolBar

Google提供了Navigation与ToolBar连接的功能,代码如下:

val navController = findNavController(R.id.nav_host_fragment_content_main)
appBarConfiguration = AppBarConfiguration(navController.graph)
setupActionBarWithNavController(navController, appBarConfiguration)

上面我们提到,如果Navigation与ToolBar连接,标题栏会自动显示在导航视图中设定好的label。

注意这里的findNavController是Activity的扩展函数,它最终一样会调用Navigation的对应函数,所以与Fragment的流程是一样的。而上面我们提到了,在NavHostFragment中给上层View也设置了tag,所以在这里才能获取到NavController。

除了这个,我们还可以发现当在切换页面的时候,标题栏的返回按钮也会自动显示和隐藏。当导航到第二个页面SecondFragment,返回按钮显示;当回退到首页时,返回按钮隐藏。

但是此时返回按钮点击无效,因为我们还需要重写一个函数:

override fun onSupportNavigateUp(): Boolean {
    val navController = findNavController(R.id.nav_host_fragment_content_main)
    return navController.navigateUp(appBarConfiguration)
            || super.onSupportNavigateUp()
}

这样当点击标题栏的返回按钮时,会执行NavController的navigateUp函数,就会退回到上一页面。

总结

可以看出通过Google推出的这个Navigation,可以让开发者更加优雅管理导航,同时也简化了这部分的开发工作,可视化功能可以让开发者更直观的进行管理。除此之外,Google还提供了Safe Args Gradle插件,该插件可以生成简单的对象和构建器类,这些类支持在目的地之间进行类型安全的导航和参数传递。关于这个大家可以参考官方文档developer.android.google.cn/guide/navig… 即可。

以上就是一文详解Jetpack Android新一代导航管理Navigation的详细内容,更多关于Jetpack Android导航管理Navigation的资料请关注我们其它相关文章!

(0)

相关推荐

  • Android Jetpack导航组件Navigation创建使用详解

    目录 引言 依赖项 创建导航图 导航宿主 导航到目的地 传递参数 NavigationUI 多模块导航 引言 导航是指支持用户导航.进入和退出应用中不同内容片段的交互.Android Jetpack 的导航组件可实现导航,无论是简单的按钮点击,还是应用栏和抽屉式导航栏等更为复杂的模式,该组件均可应对. 依赖项 def nav_version = "2.5.2" implementation "androidx.navigation:navigation-fragment-kt

  • Android开发Jetpack Compose元素Modifier特性详解

    目录 正文 有序性 不可变性 正文 本文将会介绍Jetpack Compose中的Modifier.在谷歌官方文档中它的描述是这么一句话:Modifier元素是一个有序.不可变的集合,它可以往Jetpack Compose UI元素中添加修饰或者各种行为.例如,背景.填充和单击事件监听器装饰或添加行为到文本或按钮.本文将会从修饰符的两个特性有序和不可变入手来探究修饰符的应用,以下是本文目录: 有序性 不可变性 有序性 官方对修饰符定义的这个特性包含两个层面的意思,一是修饰符的使用是链式的它是有先

  • Android Jetpack 组件LiveData源码解析

    目录 前言 基本使用 疑问 源码分析 Observer ObserverWrapper LifecycleBoundObserver MutableLiveData postValue setValue 问题答疑 LiveData 特性引出的问题 问题解决 最后 前言 本文来分析下 LiveData 的源码,以及其在实际开发中的一些问题. 基本使用 一般来说 LiveData 都会配合 ViewModel 使用,篇幅原因关于 ViewModel 的内容将在后续博客中分析,目前可以将 ViewMo

  • Android开发快速实现底部导航栏示例

    目录 Tint 着色器 依赖(AndroidX) 布局 编写渲染颜色选择器-tint_selector_menu_color menu 文件中 icon-nav_bottom_menu BottomNavigationView的点击事件 配合ViewPager实现Tab栏 对应的适配器 Tint 着色器 优点:去除“无用”图片,节省空间 配合BottomNavigationView,实现一个快速,简洁的Tab栏 传统做法:Tab 切换,字体变色.图片变色.至少给我提供八张图,四张默认,四张选中,

  • 一文详解C++中动态内存管理

    目录 前言 1.C/C++程序的内存开辟 2.C语言中动态内存管理方式:malloc/calloc/realloc/free 2.1malloc.calloc.realloc区别? 3.C++内存管理方式 3.1 new/delete操作内置类型 3.2 new和delete操作自定义类型 3.3new和malloc处理失败 4.operator new与operator delete函数 4.1 operator new与operator delete函数 4.1.1 我们看看operator

  • 一文详解 Compose Navigation 的实现原理

    目录 前言 1. 从 Jetpack Navigation 说起 2. 定义导航 3. 导航跳转 4. 保存状态 SaveableStateHolder & rememberSaveable 导航回退时的状态保存 底部导航栏切换时的状态保存 5. 导航转场动画 6. Hilt & Navigation 7. 总结 前言 一个纯 Compose 项目少不了页面导航的支持,而 navigation-compose 几乎是这方面的唯一选择,这也使得它成为 Compose 工程的标配二方库.介绍 

  • 详解Flutter的路由导航

    Flutter 的路由导航 路由管理或导航管理:从一个页面平滑地过渡到另一个页面,我们需要有一个统一的机制来管理页面之间的跳转.在原生的Android 开发,是通过startActivity或startActivityForResult 来完成页面的跳转的,在Flutter 中如何实现呢? 在 Flutter 中,页面之间的跳转是通过 Route 和 Navigator 来管理的: Route 是页面的抽象,主要负责创建对应的界面,接收参数,响应 Navigator 打开和关闭: 而 Navig

  • 一文详解Python中PO模式的设计与实现

    目录 什么是PO模式 PO 三层模式 PO 设计模式的优点 将改写的脚本转为PO设计模式 构建基础的 BasePage 层 构建首页的 Page 层(HomePage) 构建登录页的 Page 层(LoginPage) 构建 首页 - 订单 - 支付 流程的 Page 层(OrderPage) PO 设计模式下测试Case的改造 在使用 Python 进行编码的时候,会使用自身自带的编码设计格式,比如说最常见的单例模式,稍微抽象一些的抽象工厂模式等等… 在利用 Python 做自动化测试的时候,

  • 详解Kotlin Android开发中的环境配置

    详解Kotlin Android开发中的环境配置 在Android Studio上面进行安装插件 在Settings ->Plugins ->Browse repositores.. ->kotlin 安装完成后重启Android Studio就生效了 如图所示: 在Android Studio中做Kotlin相关配置 (1)在根目录 的build.gradle中进行配置使用,代码如下: buildscript { ext.kotlin_version = '1.1.2-4' repos

  • 详解xamarin Android 实现ListView万能适配器

    详解xamarin Android 实现ListView万能适配器 早些时候接触xamarin Android 的列表,写了很多ListView的Adapter,建一个ListView就写一个Adapter,每一个Adapter里面还有去写一个ViewHolder的类来优化,自从看了hongyang博客的listview万能适配器的文章,学习良多,所以就写篇关于xamarin android ListView通用适配器的文章. 本章主要分为以下三点: 打造通用的ViewHolder优化ListV

  • 详解给Vue2路由导航钩子和axios拦截器做个封装

    1.写在前面 最近在学习Vue2,遇到有些页面请求数据需要用户登录权限.服务器响应不符预期的问题,但是总不能每个页面都做单独处理吧,于是想到axios提供了拦截器这个好东西,再于是就出现了本文. 2.具体需求 用户鉴权与重定向:使用Vue提供的路由导航钩子 请求数据序列化:使用axios提供的请求拦截器 接口报错信息处理:使用axios提供的响应拦截器 3.简单实现 3.1 路由导航钩子层面鉴权与重定向的封装 路由导航钩子所有配置均在router/index.js,这里是部分代码 import

  • 详解Xamarin.Android 利用Fragment实现底部菜单

    本篇文章主要介绍了详解Xamarin.Android 利用Fragment实现底部菜单,分享给大家,具体如下: 效果图: 第一步:添加引用 引用 Crosslight.Xamarin.Android.Support.v7.AppCompat 这个包. 第二步:绘制Main和Fragment界面 fg_home.axml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:androi

  • 详解基于Android的Appium+Python自动化脚本编写

    1.Appium Appium是一个开源测试自动化框架,可用于原生,混合和移动Web应用程序测试, 它使用WebDriver协议驱动iOS,Android和Windows应用程序. 通过Appium,我们可以模拟点击和屏幕的滑动,可以获取元素的id和classname,还可以根据操作生成相关的脚本代码. 下面开始Appium的配置. appPackage和APPActivity的获取 任意下载一个app 解压 但是解压出来的xml文件可能是乱码,所以我们需要反编译文件. 逆向AndroidMan

  • 详解EFCore中的导航属性

    使用了这么久的EntityFrameworkCore框架,今天想来就其中的一个部分来做一个知识的梳理,从而使自己对于整个知识有一个更加深入的理解,如果你对EFCore中的实体关系不熟悉你需要有一个知识的预热,这样你才能够更好的去理解整个知识,在建立好了这些实体之间的关系以后,我们可以通过使用InClude.ThenInclude这些方法来进行快速获得对应关联实体数据,用起来确实十分的方便,这里我们将通过一系列的例子来进行说明.    1 单独使用Include 在介绍这个方法之前,我来先贴出实体

随机推荐