美文网首页
Android Jetpack架构组件(九)— Navigati

Android Jetpack架构组件(九)— Navigati

作者: 独自闯天涯的码农 | 来源:发表于2022-08-07 20:09 被阅读0次

一、Navigation简介

Navigation 直接翻译即为导航,它是 Android Jetpack 组件之一。
导航是指支持用户导航、进入和退出应用中不同内容片段的交互。Android Jetpack 的导航组件可帮助您实现导航,无论是简单的按钮点击,还是应用栏和抽屉式导航栏等更为复杂的模式,该组件均可应对。导航组件还通过遵循一套既定原则来确保一致且可预测的用户体验。

Navigation 让单 Activity 应用成为首选架构。应用内Fragment页面的跳转则由 Navigation 来处理,开发者无需在处理 FragmentTransaction 的复杂性以及相关的转场动画。

二、Navigation使用

1、在app的gradle.build中添加依赖

dependencies {
  def nav_version = "2.5.0"

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

  // Feature module Support
  implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version"

  // Testing Navigation
  androidTestImplementation "androidx.navigation:navigation-testing:$nav_version"

  // Jetpack Compose Integration
  implementation "androidx.navigation:navigation-compose:$nav_version"
}

2、创建导航图xml文件

在需要创建的module中选择res,创建 Android Resource File


2.png

在弹出的窗口中。设置名称。选择 Navigation 即可在 res/navigation下创建出一个开发者设置的导航图

3.png

创建的nav_graph.xml文件

<?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">

</navigation>

3、创建导航图内容

4.png
通过 Navigation Graph (导航图) 提供的快捷方式去添加。

点击添加目的地小图标 Create new desination 即可创建新的 desination 即(目的地), 当然还可以从列表直接选择之前已经创建好的。


5.png
<?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"
    android:id="@+id/nav_graph"
    app:startDestination="@id/homeFragment">

    <fragment
        android:id="@+id/homeFragment"
        android:name="com.alan.mynavigation.HomeFragment"
        android:label="fragment_home"
        tools:layout="@layout/fragment_home" />

    <fragment
        android:id="@+id/secondFragment"
        android:name="com.alan.mynavigation.SecondFragment"
        android:label="fragment_second"
        tools:layout="@layout/fragment_second" />

</navigation>

xml字段含义

navigation标签

字段 含义
android:id="@+id/nav_graph" 创建的 xml 名称
app:startDestination 表示起始导航图,会显示哪个界面,会有一个小房子标志

fragment标签

字段 含义
android:name 目的地对应的类
android:lable 包含该目的地的 XML 布局文件的名称
tools:layout 设置可以预览导航图目的地布局文件

4、将导航图添加到布局当中

在MainActivity的xml中添加 FragmentContainerView

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/fcv_nav"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:name="androidx.navigation.fragment.NavHostFragment"
        app:defaultNavHost="true"
        app:navGraph="@navigation/nav_graph" />

</androidx.constraintlayout.widget.ConstraintLayout>

FragmentContainerView标签

字段 含义
android:id 必填,否则报错
android:name 属性包含 NavHost 实现的类名称。
app:navGraph 属性将 NavHostFragment 与导航图相关联
app:defaultNavHost="true" 确保您的 NavHostFragment 会拦截系统返回按钮。请注意,只能有一个默认 NavHost。若为true,需要的Activity中重写onSupportNavigateUp方法。
override fun onSupportNavigateUp(): Boolean {
  //调用扩展函数findNavController
  return findNavController(R.id.nav_host_fragment).navigateUp()
}  

5、普通跳转并传参数

1.创建跳转行为

切换到Design 标签页中,将鼠标悬停在目的地的右侧,该目的地右侧上方会显示一个圆圈,点击您希望用户导航到的目的地,并将光标拖动到该目的地的上方,然后松开。这两个目的地之间生成的线条表示操作完成,并在xml 中会在起点的fragment下生成一个<action>标签,其中 app:destination表示目的地,design页面右边属性也可看到详情。

<fragment
        android:id="@+id/homeFragment"
        android:name="com.alan.mynavigation.HomeFragment"
        android:label="fragment_home"
        tools:layout="@layout/fragment_home" >
        <action
            android:id="@+id/action_homeFragment_to_secondFragment"
            app:destination="@id/secondFragment" />
    </fragment>
12.gif
2.无参跳转
  • Fragment.findNavController().navigate(ResId)
//点击事件
btn_second.setOnClickListener {
  //通过传入action来实现导航 
  findNavController().navigate(R.id.action_homeFragment_to_secondFragment)
}
  • View.findNavController().navigate(ResId)
//点击事件
btn_second.setOnClickListener {
  //通过传入action来实现导航
  it.findNavController().navigate(R.id.action_homeFragment_to_secondFragment)
}
  • Activity.findNavController().navigate(ResId)
//点击事件
btn_second.setOnClickListener {
  //通过传入action来实现导航 
  findNavController().navigate(R.id.action_homeFragment_to_secondFragment)
}

其中的 resId 是上面我们创建的<action>标签中的id

获取NavController方式不同
//注意:Kotlin使用和Java中获取NavController方式不同
//Kotlin:
Fragment.findNavController()
View.findNavController()
Activity.findNavController(viewId: Int)

//Java:
NavHostFragment.findNavController(Fragment)
Navigation.findNavController(Activity, @IdRes int viewId)
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

3.带参跳转

不推荐的方式。推荐的方式可以参考 safe ages跳转传参。

//点击事件
btn_second.setOnClickListener {
  val bundle = Bundle()
  bundle.putString("username", "alan")
  bundle.putInt("age", 18)
  //通过传入action来实现导航
  it.findNavController().navigate(R.id.action_homeFragment_to_secondFragment,bundle)
}

获取的时候和Fragment以前的方式一样,使用 arguments 去获取。

6、safe ages跳转传参

Navigation 组件具有一个名为 Safe Args 的 Gradle 插件,该插件可以生成简单的 object 和 builder 类,以便以类型安全的方式浏览和访问任何关联的参数。我们强烈建议您将 Safe Args 用于导航和数据传递,因为它可以确保类型安全。

1.配置

在根目录build.gradle中先添加 plugin

buildscript {
    repositories {
        google()
    }
    dependencies {
        def nav_version = "2.3.2"
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
    }
}

必须应用以下两个可用插件之一,请将以下行添加到应用或模块的 build.gradle 文件中

plugins {
    //生成适用于 Java 模块或 Java 和 Kotlin 混合模块的 Java 语言代码
    id("androidx.navigation.safeargs.kotlin")
    //生成仅适用于 Kotlin 模块的 Kotlin 语言代码
    id("androidx.navigation.safeargs.kotlin")
}

配置完成以后记得要clean rebuild 一下,会生成对应的Directions类。


image.png

启用 Safe Args 后,生成的代码会为每个操作包含以下类型安全的类和方法,以及每个发送和接收目的地。
1.为生成操作的每一个目的地创建一个类。该类的名称是在源目的地的名称后面加上“Directions”。例如,如果源目的地是名为 SpecifyAmountFragment 的 Fragment,则生成的类的名称为 SpecifyAmountFragmentDirections。
2.该类会为源目的地中定义的每个操作提供一个方法。
对于用于传递参数的每个操作,都会创建一个 inner 类,该类的名称根据操作的名称确定。例如,如果操作名称为 confirmationAction,,则类名称为 ConfirmationAction。如果您的操作包含不带 defaultValue 的参数,则您可以使用关联的 action 类来设置参数值。
3.为接收目的地创建一个类。该类的名称是在目的地的名称后面加上“Args”。例如,如果目的地 Fragment 的名称为 ConfirmationFragment,,则生成的类的名称为 ConfirmationFragmentArgs。可以使用该类的 fromBundle() 方法检索参数。

2.无参跳转
btn_second.setOnClickListener {
  val action = HomeFragmentDirections.actionHomeFragmentToSecondFragment()
  findNavController().navigate(action)
}
3.有参跳转
第一步:创建参数

Design 模式下,点击启示位置(注意是启示位置)布局信息,看到他变成蓝色以后。看到右边面板上有 Arguments


image.png

创建了两个字段。name age 对应的导航图中也可以看到其信息。

<fragment
        android:id="@+id/homeFragment"
        android:name="com.alan.mynavigation.HomeFragment"
        android:label="fragment_home"
        tools:layout="@layout/fragment_home" >
        <action
            android:id="@+id/action_homeFragment_to_secondFragment"
            app:destination="@id/secondFragment" />
        <argument
            android:name="name"
            app:argType="string"
            android:defaultValue="alan" />
        <argument
            android:name="age"
            app:argType="integer"
            android:defaultValue="18" />
    </fragment>
argument标签
字段 含义
android:name 参数名称
android:defaultValue 为默认值
app:argType 为参数类型
app:nullable 为标记是否可空
第二步:生成Args类

创建完成clean rebuild 一下,插件会给我们生成一个 Args结尾的类。


image.png
第三步:带参数跳转
btn_second.setOnClickListener {
  val bundle = HomeFragmentArgs("张三",20).toBundle()
  findNavController().navigate(R.id.action_homeFragment_to_secondFragment,bundle)
}
第四步:接收参数
//方式1:使用 fromBundle
arguments?.apply {
  val fragmentArgs = HomeFragmentArgs.fromBundle(this)
  val username = fragmentArgs.name
  val age = fragmentArgs.age
  tv_info.text = "$username $age"
}


//方式2: 使用委托的方式
val args by navArgs<HomeFragmentArgs>()

 tv_info.text = "$username $age"
4.使用Action方式有参跳转

从上个列子可以看出虽然携带参数跳转了,但使用的是id

findNavController().navigate(R.id.action_homeFragment_to_secondFragment,bundle)

插件为我们生成的HomeFragmentDirections不可以携带参数,这是因为需要跳转双方都声明相同的参数。然后clean rebuild一下生成新的带参数的HomeFragmentDirections。


image.png
btn_second.setOnClickListener {
    val action = HomeFragmentDirections.actionHomeFragmentToSecondFragment("张三", 20)
    findNavController().navigate(action)
}

7、Deep Links

深层链接是指将用户直接转到应用内特定目的地的链接。
比如点击一下通知就要跳转到对应的消息详情界面。

1.显式深层链接
btn_second.setOnClickListener {
    val bundle = Bundle().apply {
        putString("username", "alan")
        putInt("age", 18)
    }


    val pendingIntent = NavDeepLinkBuilder(requireActivity())
        .setGraph(R.navigation.nav_graph)
        .setDestination(R.id.secondFragment)
        .setArguments(bundle)
        //默认情况下,NavDeepLinkBuilder 会将显式深层链接启动到应用清单中声明的默认启动 Activity。如果您的 NavHost 在其他 activity 中,则您必须在创建深层链接建立工具时指定其组件名称:
        .setComponentName(DetailActivity::class.java)
        .createPendingIntent()


    //通知
    val notificationManager = requireActivity().getSystemService(NOTIFICATION_SERVICE) as (NotificationManager)

    val CHANNEL_ID = "1000"
    val channel = NotificationChannel(
        CHANNEL_ID, "测试",
        NotificationManager.IMPORTANCE_DEFAULT
    )
    notificationManager.createNotificationChannel(channel);

    val builder = NotificationCompat.Builder(requireActivity(), CHANNEL_ID)
        .setContentTitle("测试")
        .setContentText("内容")
        .setPriority(NotificationCompat.PRIORITY_DEFAULT)
        .setSmallIcon(R.drawable.ic_launcher_foreground)
        .setContentIntent(pendingIntent)
        .setAutoCancel(true)
    notificationManager.notify(1, builder.build())
}
2.创建隐式深层链接
第一步:创建隐式深层链接

可以通过 URI、intent 操作和 MIME 类型匹配深层链接。您可以为单个深层链接指定多个匹配类型,但请注意,匹配的优先顺序依次是 URI 参数、操作和 MIME 类型。例如:

<deeplink app:uri="www.example.com"
                app:action="android.intent.action.MY_ACTION"
                app:mimeType="type/subtype"/>
image.png

Add Deep Link 对话框中请注意以下几点:

  • 没有架构的 URI 会被假定为 http 或 https。例如,www.google.com 同时与 http://www.google.comhttps://www.google.com 匹配。
  • 形式为 {placeholder_name} 的路径参数占位符与一个或多个字符相匹配。例如,http://www.example.com/users/{id}http://www.example.com/users/4 匹配。Navigation 组件通过将占位符名称与已定义的参数(为深层链接目的地所定义)相匹配,尝试将占位符值解析为相应的类型。如果没有定义具有相同名称的参数,对参数值使用默认的 String 类型。您可以使用 .* 通配符匹配 0 个或多个字符。
  • 可以使用查询参数占位符代替路径参数,也可以将查询参数占位符与路径参数结合使用。例如,http://www.example.com/users/{id}?myarg={myarg}http://www.example.com/users/4?myarg=28 匹配。
  • 使用默认值或可为 null 的值所定义的变量的查询参数占位符无需匹配。例如,http://www.example.com/users/{id}?arg1={arg1}&arg2={arg2}http://www.example.com/users/4?arg2=28http://www.example.com/users/4?arg1=7 匹配。不过,路径参数并非如此。例如,http://www.example.com/users?arg1=7&arg2=28 就与上述模式不匹配,因为未提供所需的路径参数。
  • 多余的查询参数不会影响深层链接 URI 匹配。例如,即使 URI 模式中未定义 extraneousParamhttp://www.example.com/users/{id} 也与 http://www.example.com/users/4?extraneousParam=7 匹配。
  • (可选)选中 Auto Verify 可要求 Google 验证您是相应 URI 的所有者。
<fragment
        android:id="@+id/secondFragment"
        android:name="com.alan.mynavigation.SecondFragment"
        android:label="fragment_second"
        tools:layout="@layout/fragment_second">
        <argument
            android:name="name"
            android:defaultValue="alan"
            app:argType="string" />
        <argument
            android:name="age"
            android:defaultValue="18"
            app:argType="integer" />
        <deepLink
            android:id="@+id/deepLink"
            app:uri="http://www.alan.com/second/${name}" />
    </fragment>
第二步:添加nav-graph 声明

应用的manifest.xml 文件中,将一个 <nav-graph> 元素添加到指向现有导航图的 Activity标签中。

<activity
  android:name=".MainActivity"
  android:exported="true">
  <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>

构建项目时,Navigation 组件会将 <nav-graph> 元素替换为生成的 <intent-filter> 元素,以匹配导航图中的所有深层链接。

在触发隐式深层链接时,返回堆栈的状态取决于是否使用Intent.FLAG_ACTIVITY_NEW_TASK标志启动隐式 Intent:

  • 如果该标志已设置,任务返回堆栈就会被清除,并被替换为相应的深层链接目的地。与显式深层链接一样,当嵌套图表时,每个嵌套级别的起始目的地(即层次结构中每个 <navigation> 元素的起始目的地)也会添加到相应堆栈中。也就是说,当用户从深层链接目的地按下返回按钮时,他们会返回到相应的导航堆栈,就像从入口点进入您的应用一样。
  • 如果该标记未设置,您仍会位于触发隐式深层链接时所在的上一个应用的任务堆栈中。在这种情况下,如果按下返回按钮,您会返回到上一个应用;如果按下向上按钮,就会在导航图中的父级目的地上启动应用的任务。

使用 Navigation 时,强烈建议您始终使用默认launchMode:standard。使用standard启动模式时,Navigation 会调用handleDeepLink()来处理Intent中的任何显式或隐式深层链接,从而自动处理深层链接。但是,如果在使用备用 singleTop 等备选 launchMode 时重复使用了相应 Activity,则这不会自动发生。在这种情况下,有必要在 onNewIntent() 中手动调用 handleDeepLink(),如以下示例所示:

override fun onNewIntent(intent: Intent?) {
    super.onNewIntent(intent)
    navController.handleDeepLink(intent)
}
第三步:使用隐式深层链接
btn_second.setOnClickListener {
    val name = "李四"
    findNavController().navigate("http://www.alan.com/second/${name}".toUri())
}

8、添加动画效果

<?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"
    android:id="@+id/nav_graph"
    app:startDestination="@id/homeFragment">

    <fragment
        android:id="@+id/homeFragment"
        android:name="com.alan.mynavigation.HomeFragment"
        android:label="fragment_home"
        tools:layout="@layout/fragment_home" >
        <action
            android:id="@+id/action_homeFragment_to_secondFragment"
            app:destination="@id/secondFragment"
            app:enterAnim="@anim/nav_default_enter_anim"
            app:exitAnim="@anim/nav_default_exit_anim"
            app:popEnterAnim="@anim/nav_default_pop_enter_anim"
            app:popExitAnim="@anim/nav_default_pop_exit_anim" />
        <argument
            android:name="name"
            app:argType="string"
            android:defaultValue="alan" />
        <argument
            android:name="age"
            app:argType="integer"
            android:defaultValue="18" />
    </fragment>

    <fragment
        android:id="@+id/secondFragment"
        android:name="com.alan.mynavigation.SecondFragment"
        android:label="fragment_second"
        tools:layout="@layout/fragment_second">
        <argument
            android:name="name"
            android:defaultValue="alan"
            app:argType="string" />
        <argument
            android:name="age"
            android:defaultValue="18"
            app:argType="integer" />
        <deepLink
            android:id="@+id/deepLink"
            app:uri="http://www.alan.com/second/{name}" />
    </fragment>


</navigation>

参考:Android 开发者网站

相关文章

网友评论

      本文标题:Android Jetpack架构组件(九)— Navigati

      本文链接:https://www.haomeiwen.com/subject/gcukirtx.html