【Android Jetpack】Navigation的使用

引入

单个Activity嵌套多个Fragment的UI架构模式,非常非常普遍。但是,对Fragment的管理一直是一件比较麻烦的事情。工程师需要通过FragmentManagerFragmentTransaction来管理Fragment之间的切换。页面的切换通常还包括对应用程序App bar的管理、Fragment间的切换动画,以及Fragment间的参数传递。纯代码的方式使用起来不是特别友好,并且Fragment和App bar在管理和使用的过程中显得很混乱。

为此,Jetpack提供了一个名为Navigation的组件,旨在方便我们管理页面和App bar。

Navigation具有以下优势:

  • 可视化的页面导航图,类似于Apple Xcode中的StoryBoard,便于我们理清页面间的关系。
  • 通过destination和action完成页面间的导航。
  • 方便添加页面切换动画。
  • 页面间类型安全的参数传递。
  • 通过NavigationUI类,对菜单、底部导航、抽屉菜单导航进行统一的管理。
  • 支持深层链接DeepLink。

Navigation 组件旨在用于具有一个主 Activity 和多个 Fragment 目的地的应用。主 Activity 与导航图相关联,且包含一个负责根据需要交换目的地的 NavHostFragment。在具有多个 Activity 目的地的应用中,每个 Activity 均拥有其自己的导航图。

主要元素

导航组件由以下三个关键部分组成:

  1. Navigation Graph

    在一个集中位置包含所有导航相关信息的 XML 资源。这包括应用内所有单个内容区域(称为目标)以及用户可以通过应用获取的可能路径。

  2. NavHost

    显示导航图中目标的空白容器。导航组件包含一个默认NavHost实现 (NavHostFragment),可显示Fragment目标。

  3. NavController

    在NavHost中管理应用导航的对象。当用户在整个应用中移动时,NavController会安排NavHost中目标内容的交换。

在应用中导航时,您告诉NavController,您想沿导航图中的特定路径导航至特定目标,或直接导航至特定目标。NavController便会在NavHost中显示相应目标。

例如利用 BottomNavigationView + Fragment/FragmentContainerView 的组合实现底部导航栏

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/btmnv"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:background="@drawable/jianbian0"
        app:itemRippleColor="@color/colorDeep"
        app:itemIconTint="@drawable/btmnv_select"
        app:itemTextColor="@drawable/btmnv_select"
        app:menu="@menu/btmnvmenu"
        app:labelVisibilityMode="selected"
        app:itemBackground="@null"
        />

    <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:navGraph="@navigation/mobile_navigation" />

代码:

         val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
         binding.btmnv.setupWithNavController(navHostFragment.navController)

Navigation Graph

导航图是一种资源文件,其中包含您的所有目的地和操作。该图表会显示应用的所有导航路径。

向项目添加导航图:

【Android Jetpack】Navigation的使用_第1张图片




元素是导航图的根元素。当您向图表添加目的地和连接操作时,可以看到相应的 元素在此处显示为子元素。如果您有,它们将显示为子 元素。

向 Activity 添加 NavHost

导航宿主是 Navigation 组件的核心部分之一。导航宿主是一个空容器,用户在您的应用中导航时,目的地会在该容器中交换进出。

导航宿主必须派生于 NavHost。Navigation 组件的默认 NavHost 实现 (NavHostFragment) 负责处理 Fragment 的更换。

静态添加

   <androidx.fragment.app.FragmentContainerView
        .....
        android:name="androidx.navigation.fragment.NavHostFragment"
        app:defaultNavHost="true"
        app:navGraph="@navigation/nav_graph" />

NavHostFragment是一个特殊的Fragment(android:name所定义的),我们需要将其添加到Activity的布局文件中,作为其他Fragment的容器,然后定义Fragment。

  • android:name :包含 NavHost 实现的类名称。
  • app:navGraph :将 NavHostFragment导航图相关联。导航图会在此 NavHostFragment 中指定用户可以浏览的碎片。
  • app:defaultNavHost="true" :确保您的 NavHostFragment 会自动处理系统返回键,即当用户按下手机的返回按钮时,系统能自动将当前所展示的Fragment退出。并且只能有一个默认 NavHost

<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"
    app:startDestination="@id/theFirstFragment"> // 最开始所展示的Fragment
    <fragment
        android:id="@+id/theFirstFragment"
        ...... />
navigation>

导航到目的地

导航到目的地是使用 NavController 完成的,它是一个在 NavHost 中管理应用导航的对象。每个 NavHost 均有自己的相应 NavController。您可以使用以下方法之一检索 NavController

Kotlin

  • Fragment.findNavController()
  • View.findNavController()
  • Activity.findNavController(viewId: Int)

Java

  • NavHostFragment.findNavController(Fragment)
  • [Navigation.findNavController(Activity, @IdRes int viewId)](https://developer.android.com/reference/androidx/navigation/Navigation#findNavController(android.app.Activity, int))
  • Navigation.findNavController(View)

使用 FragmentContainerView 创建 NavHostFragment,或通过 FragmentTransaction 手动将 NavHostFragment 添加到您的 Activity 时,尝试通过 Navigation.findNavController(Activity, @IdRes int) 检索 Activity 的 onCreate() 中的 NavController 将失败。您应改为直接从 NavHostFragment 检索 NavController。(没用过,不清楚,搬得)

val navHostFragment =
        supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
navController.navigate(R.id.action_blankFragment_to_blankFragment2)

对于按钮,您还可以使用 Navigation 类的 createNavigateOnClickListener() 便捷方法导航到目的地,如下例所示:

button.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.theAimFragment, null))

使用 DeepLinkRequest 导航

您可以使用 navigate(NavDeepLinkRequest) 直接导航到隐式深层链接目的地,如下例所示:

val request = NavDeepLinkRequest.Builder
    .fromUri("android-app://androidx.navigation.app/profile".toUri())
    .build()
findNavController().navigate(request)

返回堆栈

Android 会维护一个返回堆栈,其中包含您之前访问过的目的地。当用户打开应用时,应用的第一个目的地就放置在堆栈中。每次调用 navigate() 方法都会将另一目的地放置到堆栈的顶部。点按向上返回会分别调用 NavController.navigateUp()NavController.popBackStack() 方法,用于移除(或弹出)堆栈顶部的目的地。

NavController.popBackStack() 会返回一个布尔值,表明它是否已成功返回到另一个目的地。当返回 false 时,最常见的情况是手动弹出图的起始目的地。

如果该方法返回 false,则 NavController.getCurrentDestination() 会返回 null。您应负责导航到新目的地,或通过对 Activity 调用 finish() 来处理弹出情况,如下例所示:

if (!navController.popBackStack()) {
    // Call finish() on your Activity
    finish()
}

通过 引用其他导航图

在导航图中,您可以使用 include 引用其他图。虽然这在功能上与使用嵌套图相同,但 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"
    app:startDestination="@id/fragment">

    <include app:graph="@navigation/included_graph" />

    <fragment
        android:id="@+id/fragment"
        android:name="com.example.myapplication.BlankFragment"
        android:label="Fragment in Root Graph"
        tools:layout="@layout/fragment_blank">
        <action
            android:id="@+id/action_fragment_to_second_graph"
            app:destination="@id/second_graph" />
    fragment>

    ...
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/second_graph"
    app:startDestination="@id/includedStart">

    <fragment
        android:id="@+id/includedStart"
        android:name="com.example.myapplication.IncludedStart"
        android:label="fragment_included_start"
        tools:layout="@layout/fragment_included_start" />
navigation>

创建全局操作

您可以使用全局操作来创建可由多个目的地共用的通用操作。例如,您可能想要不同目的地中的多个按钮导航到同一应用主屏幕。


<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"
            android:id="@+id/main_nav"
            app:startDestination="@id/mainFragment">

  ...

  <action android:id="@+id/action_global_mainFragment"
          app:destination="@id/mainFragment"/>

navigation>

如需在代码中使用某个全局操作,请将该全局操作的资源 ID 传递到每个界面元素的 navigate() 方法,如以下示例所示:

viewTransactionButton.setOnClickListener { view ->
    view.findNavController().navigate(R.id.action_global_mainFragment)
}

使用 Bundle 传递参数

我们可以使用 Bundle 对象在目的地之间传递参数。创建 Bundle 对象并调用 navigate() 将它传递给目的地:

val bundle = bundleOf("amount" to amount)view.findNavController().navigate(R.id.confirmationAction, bundle)

在接收目的地的代码中,请使用 getArguments() 方法来检索 Bundle 并使用其内容:

val tv = view.findViewById<TextView>(R.id.textViewAmount)
tv.text = arguments?.getString("amount")

NavigationUI

导航图是Navigation组件中很重要的一部分,它可以帮助我们快速了解页面之间的关系,再通过NavController便可以完成页面的切换工作。而在页面的切换过程中,通常还伴随着App bar中menu菜单的变化。对于不同的页面,App bar中的menu菜单很可能是不一样的。App bar中的各种按钮和菜单,同样承担着页面切换的工作。例如,当ActionBar左边的返回按钮被单击时,我们需要响应该事件,返回到上一个页面。既然Navigation和App bar都需要处理页面切换事件,那么,为了方便管理,Jetpack引入了NavigationUI组件,使App bar中的按钮和菜单能够与导航图中的页面关联起来。

NavigationUI 支持以下顶部应用栏类型:

  • Toolbar
  • CollapsingToolbarLayout
  • ActionBar
override fun onCreate(savedInstanceState: Bundle?) {
    ...

    val navHostFragment =
        supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
    val navController = navHostFragment.navController
    val appBarConfiguration = AppBarConfiguration(
        topLevelDestinationIds = setOf(),
        fallbackOnNavigateUpListener = ::onSupportNavigateUp
    )
    findViewById<Toolbar>(R.id.toolbar)
        .setupWithNavController(navController, appBarConfiguration)
}

你可能感兴趣的:(Jetpack,android,jetpack,android,xml,android,studio)