Jetpack系列 — Navigation

Posted 网易在职程序猿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Jetpack系列 — Navigation相关的知识,希望对你有一定的参考价值。

Jetpack系列(一) — Navigation

背景介绍

​ 随着android开发的快速发展,技术选型众多,Google希望实现一套工具集合帮助开发者遵循最佳做法,减少样板代码并编写可在各种 Android 版本和设备中一致运行的代码,让开发者精力集中编写重要的代码,于是提出了JetpackJetpack分包括四大模块分别是ArchitectureFoundationyBehaviorUI

Foundation 是基础组件,提供底层功能,如向后兼容性、测试、Kotlin支持等。包括Android KTXAppCompatMultiDexTestBenchmark

Architecture 是架构组件,帮助开发者设计稳健、可测试且易维护的应用,包括Data BindingLifecyclesLiveDataNavigationPagingRoomViewModelWorkManager等。

Behavior是行为组件,与标准Android服务相集成,包括CameraXDownloadManagerPermissionsNotifications等。

UI 是界面组件,包含用于常见效果的内置动画、表情符号,其中使用Compose完成的界面也是UI的范畴。

Jetpack组件众多,咱们先从Architecture开始,逐个讲解。首先来看一看NavigationNavigation能够简化导航过程,通过图形化界面构建界面之间的跳转关系。

Navigation简单介绍

初步印象

Navigation 可实现按钮点击、应用栏和抽屉式导航栏等导航

基本概念

Navigation包括三部分:Navigation GraphNavHostFragmentNavController ,后面在具体的代码中讲解。

Navigation Graph 是XML文件,在navigation文件夹下。在Navigation Graph文件内进行路径的设置。这里需要注意:<navigation> 包含一个或多个目的地,可以由 <activity><fragment> 元素表示

NavHostFragment 是容器,负责装载Navigation Graph

NavController 负责具体的触发跳转,调用 navigate()popBackStack(), 等方法时,它会根据正在导航的目标目的地类型或起始目的地类型来将这些命令转换为相应的框架操作,当目的地是activity时,自动调用 startActivity()

Navigation基本使用

基本跳转

  1. 创建Navigation Graph ,Studio提供了直接创建的插件,工程根目录右键New->Android Resource File ->Resource type 选中Navigation -> 文件名称,这里会自动添加这两个库

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

  2. Navigation Graph 文件右上角 New Destination 添加视图

  3. 添加NavHostFragment,可以是fragment,也可以是androidx.fragment.app.FragmentContainerView 推荐后者。

  4. NavController 触发跳转事件

    // 跳转方式二 actionId
    binding.button.setOnClickListener(
        Navigation.createNavigateOnClickListener(R.id.next_action)
    )
    binding.button.setOnClickListener {
        // 跳转方式一 id
        findNavController().navigate(R.id.flow_step_fragment, null)
        // 跳转方式三 action
        val directions = HomeFragmentDirections.nextAction()
        findNavController().navigate(directions)
    }
    
  5. 传递参数

    传递参数使用 safe args 传参是目的地设置参数

    • 打开项目 build.gradle 文件,添加 safe args 插件

      classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigationVersion"
      
    • 打开 app/build.gradle 文件,应用插件

      apply plugin: 'androidx.navigation.safeargs.kotlin'
      
    • Graph 文件 中目的地添加参数,这里序列化实例推荐Parcelable方式

    // 发送方
    val flowStepNumber = 1
    val product = Product("可乐")
    val directions = HomeFragmentDirections.nextAction(flowStepNumber, product)
    findNavController().navigate(directions)
    
    // 接受方
    private val safeArgs: FlowStepFragmentArgs by navArgs()  // 全局变量
    
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        val flowStepNumber = safeArgs.flowStepNumber
        val product = safeArgs.product
        Log.d("====", product?.name.toString())
        binding = when (flowStepNumber) {
            2 -> FragmentFlowStepTwoBinding.inflate(inflater, container, false)
            else -> FragmentFlowStepOneBinding.inflate(inflater, container, false)
        }
        return binding.root
    }
    
  6. 动画

​ 两种添加转场动画的方式

​ 方式一: xml 当中

 <action
        android:id="@+id/next_action"
        app:destination="@id/flow_step_one_dest"
        app:enterAnim="@anim/slide_in_right"
        app:exitAnim="@anim/slide_out_left"
        app:popEnterAnim="@anim/slide_in_left"
        app:popExitAnim="@anim/slide_out_right" />

​ 方式二:逻辑代码当中

val options = navOptions {
    anim {
        enter = R.anim.slide_in_right
        exit = R.anim.slide_out_left
        popEnter = R.anim.slide_in_left
        popExit = R.anim.slide_out_right
    }
}

val flowStepNumber = 1
val product = Product("可乐")
val directions = HomeFragmentDirections.nextAction(flowStepNumber, product)
findNavController().navigate(directions, options)

菜单跳转

NavigationUI 具有将菜单项与导航目的地相关联的静态方法,navigation-ui-ktx 是执行关联操作的一组扩展函数。如果 NavigationUI 在当前导航图上找到与目的地具有相同 ID 的菜单项,就会将该菜单项配置为导航到该目的地。

步骤一: 创建menu文件 overflow_menu.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/settings_dest"
        android:icon="@drawable/ic_settings"
        android:menuCategory="secondary"
        android:title="@string/settings" />
</menu>

步骤二: 添加 menu 到菜单,添加选中事件

// 添加 menu 到菜单
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
    menuInflater.inflate(R.menu.overflow_menu, menu)
    return true
}

// menu 选中
override fun onOptionsItemSelected(item: MenuItem): Boolean {
    return item.onNavDestinationSelected(findNavController(R.id.my_nav_host_fragment))
    || super.onOptionsItemSelected(item)
}

步骤三: 修改Graph 文件,注意id 和menu文件保持一致,这里我特意把目的地设为activity,因为我最先误解只能是fragment

<activity
          android:id="@+id/settings_dest"
          android:name="com.hxdi.myapplication.SettingActivity"
          android:label="activity_seting"
          tools:layout="@layout/activity_seting" />

底部Bottom

步骤一: 添加 BottomNavigationView

<com.google.android.material.bottomnavigation.BottomNavigationView
       android:id="@+id/bottom_nav_view"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       app:layout_constraintBottom_toBottomOf="parent"
       app:menu="@menu/bottom_nav_menu"/>

步骤二: 编写menu, bottom_nav_menu.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@id/home_dest"
        android:icon="@drawable/ic_home"
        android:title="@string/home" />
    <item
        android:id="@id/deeplink_dest"
        android:icon="@drawable/ic_android"
        android:title="@string/deeplink" />
</menu>

步骤三: 关联NavController

// 底部Bottom
private fun setupBottomNavMenu(navController: NavController) {
    binding.bottomNavView.setupWithNavController(navController)
}

步骤四: 设置顶级界面,

appBarConfiguration = AppBarConfiguration(
    setOf(R.id.home_dest, R.id.deeplink_dest)   // 顶级目的地
)
setupActionBar(navController, appBarConfiguration)

侧边DrawLayout

步骤一:添加NavigationView

<androidx.drawerlayout.widget.DrawerLayout
    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">

    ...

    <com.google.android.material.navigation.NavigationView
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        app:menu="@menu/nav_drawer_menu"/>
</androidx.drawerlayout.widget.DrawerLayout>

步骤二: 编写menu,nav_drawer_menu.xml,同上, 略

步骤二: 关联NavController

appBarConfiguration = AppBarConfiguration(
    setOf(R.id.home_dest, R.id.deeplink_dest),   // 顶级目的地
    binding.drawerLayout                         // DrawerLayout
)

桌面小插件

这一小节的功能是通过桌面插件跳转至App某个页面

步骤一: 通过AppWidgetProvider创建桌面插件,AppWidgetProvider实质是BroadcastReceiver

class DeepLinkAppWidgetProvider : AppWidgetProvider() {

    override fun onUpdate(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetIds: IntArray
    ) {
        val remoteViews = RemoteViews(
            context.packageName,
            R.layout.deep_link_appwidget
        )
        val args = Bundle()
        args.putString("myarg", "From 桌面")
        val pendingIntent = NavDeepLinkBuilder(context)
            .setGraph(R.navigation.mobile_navigation)
            .setDestination(R.id.deeplink_dest)          // 跳转目的地
            .setArguments(args)                          // 参数
            .createPendingIntent()
        remoteViews.setOnClickPendingIntent(R.id.deep_link_button, pendingIntent)

        appWidgetManager.updateAppWidget(appWidgetIds, remoteViews)
    }
}

步骤二: AppWidgetProvider实质是BroadcastReceiver,在配置文件当中注册

<receiver android:name=".DeepLinkAppWidgetProvider">
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>

    <meta-data
               android:name="android.appwidget.provider"
               android:resource="@xml/deep_link_appwidget_info" />
</receiver>

步骤三: 为了观察效果,接受方也要处理一下

val myArg = arguments?.getString("myarg")
binding.text.text = myArg

网页链接跳转

NavController支持将网址直接映射到导航图中的目的地。

步骤一: 在目的地添加

<fragment
          android:id="@+id/deeplink_dest"
          android:name="com.hxdi.myapplication.DeepLinkFragment"
          android:label="@string/deeplink"
          tools:layout="@layout/fragment_deep_link">

    <argument
              android:name="myarg"
              android:defaultValue="Android!"/>

    <deepLink app:uri="www.example.com/{myarg}" />

</fragment>

步骤二: 修改配置文件, 添加nav-graph

<activity
          android:name=".MainActivity"
          android:launchMode="singleTask">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    <nav-graph android:value="@navigation/mobile_navigation" />
</activity>

通知栏跳转

本来以为到这里就结束了,但是发现例子当中还有一个通知跳转,索性也写一下,顺便复习一下通知的写法: 创建通知,NotificationManager =》 检测版本 =》 Andorid 8以上 创建通道 =》notify

 binding.button.setOnClickListener {
            val etArg = binding.etArg.text.toString()
            val args = Bundle()
            args.putString("myarg", etArg)

            val deepLink = findNavController().createDeepLink()
                .setDestination(R.id.deeplink_dest)
                .setArguments(args)
                .createPendingIntent()

            val notificationManager =
                context?.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                notificationManager.createNotificationChannel(
                    NotificationChannel(
                        "deepLink",
                        "消息",
                        NotificationManager.IMPORTANCE_HIGH
                    )
                )
            }

            val notification = NotificationCompat.Builder(requireContext(), "deepLink")
                .setContentTitle("Navigation")
                .setContentText("Deep link to Android")
                .setSmallIcon(R.drawable.ic_android)
                .setContentIntent(deepLink)
                .setAutoCancel(true)
                .build()

            notificationManager.notify(0, notification)
        }

相关知识点

知识点一: actionBar 绑定

  • 要想lable 显示 Graph 文件当中配置的文字,以及actionBar 和 NavController关联起来,需要如下步骤

    // 步骤一 显示 ActionBar
    setSupportActionBar(binding.toolbar)
    
    // 步骤二 找到 NavHostFragment
    val host: NavHostFragment = supportFragmentManager
    				.findFragmentById(R.id.my_nav_host_fragment) as NavHostFragment
    val navController = host.navController
    
    // 步骤三 关联 NavController 和 AppBarConfiguration
    appBarConfiguration = AppBarConfiguration(navController.graph) setupActionBarWithNavController(navController, appBarConfiguration)
    

知识点二: 点击事件

  • 语法层面上方式二和方式一三相同,但是actionId 只使用于方式二,这个createNavigateOnClickListener() 内部创建View点击事件有关

     // 跳转方式二 actionId
    binding.button.setOnClickListener(
        Navigation.createNavigateOnClickListener(R.id.next_action)
    )
    binding.button.setOnClickListener {
        // 跳转方式一 id
        findNavController().navigate(R.id.flow_step_fragment, null)
        // 跳转方式三 action
        val directions = HomeFragmentDirections.nextAction()
        findNavController().navigate(directions)
    }
    

知识点三:路由监听

  • NavController 提供了全局的监听接口

    // NavController 提供了全局的监听接口
    navController.addOnDestinationChangedListener { controller, destination, arguments ->
        val dest: String = try {
                  resources.getResourceName(destination.id)
            } catch (e: Resources.NotFoundException) {
                  destination.id.toString()
        }
    	Log.d("NavigationActivity", "Navigated to $dest")
    }
    

如果需要系统的jetpack学习资料,而我正好薅到这本阿里十年技术专家联合打造“最新”《Jetpack架构组件入门到精通》和《Jetpack强化实战手册》,是你学习Jetpack的葵花宝典。

点击此处蓝字免费获取

以上是关于Jetpack系列 — Navigation的主要内容,如果未能解决你的问题,请参考以下文章

4. Jetpack源码解析—LiveData的使用及工作原理

4. Jetpack源码解析—LiveData的使用及工作原理

JetPack架构---Navigation的使用

jetpack之navigation

jetpack之navigation

jetpack之navigation