Jetpack系列 — Navigation
Posted 网易在职程序猿
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Jetpack系列 — Navigation相关的知识,希望对你有一定的参考价值。
Jetpack系列(一) — Navigation
背景介绍
随着android开发的快速发展,技术选型众多,Google希望实现一套工具集合帮助开发者遵循最佳做法,减少样板代码并编写可在各种 Android 版本和设备中一致运行的代码,让开发者精力集中编写重要的代码,于是提出了Jetpack
。Jetpack
分包括四大模块分别是Architecture
、Foundationy
、Behavior
、UI
。
Foundation
是基础组件,提供底层功能,如向后兼容性、测试、Kotlin
支持等。包括Android KTX
、AppCompat
、MultiDex
、Test
、Benchmark
等
Architecture
是架构组件,帮助开发者设计稳健、可测试且易维护的应用,包括Data Binding
、Lifecycles
、LiveData
、Navigation
、Paging
、Room
、ViewModel
、WorkManager
等。
Behavior
是行为组件,与标准Android服务相集成,包括CameraX
、DownloadManager
、Permissions
、Notifications
等。
UI
是界面组件,包含用于常见效果的内置动画、表情符号,其中使用Compose完成的界面也是UI
的范畴。
Jetpack
组件众多,咱们先从Architecture
开始,逐个讲解。首先来看一看Navigation
。Navigation
能够简化导航过程,通过图形化界面构建界面之间的跳转关系。
Navigation简单介绍
初步印象
Navigation
可实现按钮点击、应用栏和抽屉式导航栏等导航
基本概念
Navigation
包括三部分:Navigation Graph、NavHostFragment、NavController ,后面在具体的代码中讲解。
Navigation Graph 是XML文件,在
navigation
文件夹下。在Navigation Graph文件内进行路径的设置。这里需要注意:<navigation>
包含一个或多个目的地,可以由<activity>
或<fragment>
元素表示
NavHostFragment 是容器,负责装载Navigation Graph。
NavController 负责具体的触发跳转,调用
navigate()
或popBackStack(),
等方法时,它会根据正在导航的目标目的地类型或起始目的地类型来将这些命令转换为相应的框架操作,当目的地是activity时,自动调用startActivity()
。
Navigation基本使用
基本跳转
-
创建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'
-
Navigation Graph 文件右上角
New Destination
添加视图 -
添加NavHostFragment,可以是fragment,也可以是
androidx.fragment.app.FragmentContainerView
推荐后者。 -
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) }
-
传递参数
传递参数使用 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 }
-
-
动画
两种添加转场动画的方式
方式一: 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的使用及工作原理