随着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
能够简化导航过程,通过图形化界面构建界面之间的跳转关系。这个是Google提供的例子github.com/googlecodel…
Navigation
包括三部分:Navigation Graph、NavHostFragment、NavController ,后面在具体的代码中讲解。
Navigation Graph 是XML文件,在navigation
文件夹下。在Navigation Graph文件内进行路径的设置。这里需要注意:<navigation>
包含一个或多个目的地,可以由 <activity>
或 <fragment>
元素表示
NavHostFragment 是容器,负责装载Navigation Graph。
NavController 负责具体的触发跳转,调用 navigate()
或 popBackStack(),
等方法时,它会根据正在导航的目标目的地类型或起始目的地类型来将这些命令转换为相应的框架操作,当目的地是activity时,自动调用 startActivity()
。
以上就可以基本地实现Navigation页面跳转了,但是在学习了Google提供的例子之后发现还有很多神奇的功能,接下来通过代码来一步一步地讲解。
基本跳转
-
创建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" />
复制代码
方式二:逻辑代码当中
// 跳转方式三 action
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)
复制代码
知识点一: 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.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")
}
复制代码
菜单跳转
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)
}
复制代码