Navigation的概念
Navigation是一套用于实现应用内部页面跳转和参数传递的组件,旨在简化Android应用的导航逻辑并推荐使用单Activity架构。
Navigation组件主要由3个部分组成:
- NavGraph导航图:包含所有导航相关信息的 XML 资源,其中包含所有目的地和操作。该图表会显示应用的所有导航路径(跳转关系);
- NavHostFragment导航宿主:导航宿主是一个空容器,用户在应用中导航时,目的地会在该容器中交换进出(可以理解为存放fragment的view容器);
- NavController:是用来编程式或声明式地控制应用内的导航流程(可以理解为用作fragment跳转的工具类);
Navigation的基本使用
-
- 首先导入Navigation依赖;
kt
implementation 'androidx.navigation:navigation-runtime-ktx:2.3.0'
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.0'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.0'
-
- 创建显示在Activity中的多个Fragment(我创建了A、B 两个Fragment);
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
tools:context=".AFragment">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="this is A fragment"
android:textSize="40dp"/>
<Button
android:id="@+id/a_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="a-btn"/>
</LinearLayout>
-
- 创建导航图;
在res中新建navigation的资源文件夹
- 创建导航图;
-
- 在NavGraph中添加Fragment,连接跳转关系;
只需在右侧添加并连接Fragment即可,左侧代码会根据右侧的状态自动生成。
- 在NavGraph中添加Fragment,连接跳转关系;
-
- 在需要添加Fragment的Activity中添加fragment控件;
kt
<fragment
android:id="@+id/fragment_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
// 指定了要使用的Fragment类是NavHostFragment
android:name="androidx.navigation.fragment.NavHostFragment"
// 指定了要使用的导航图(Navigation Graph)
app:navGraph="@navigation/navigation_main"
app:defaultNavHost="true"/>
-
- 给Fragment的Button控件添加点击事件,实现点击按钮跳转Fragment;
kt
a_btn.setOnClickListener {
// 使用Navigation.findNavController()方法来获取当前活动的NavController实例
// 然后调用navigate()方法来执行导航操作
Navigation.findNavController(requireActivity(), R.id.fragment_view).navigate(R.id.BFragment)
}
Navigation的基本使用完成,运行后点击按钮即可完成主Activity中两个Fragment之间相互切换。
ARouter概述
项目开发使用的原生路由方案一般是通过显示Intent或隐式Intent方式实现Activity 和 Fragment的跳转,随着项目的组件化、模块化开发,业务解耦,各个module不会进行相互依赖,如果moduleA和moduleB中的Activity需要相互跳转,使用传统的startActivity(intent)来进行通信是不能实现的。当然也可以通过其他方法去实现,比如隐式跳转或者反射机制实现跳转,但隐式跳转需要在Manifest中进行大量的过滤配置,不利于维护;通过反射跳转可能会对性能造成影响。因此我们来学习下路由框架ARouter。
ARouter的大致原理
ARouter使用@Route注解,在编译时期通过APT技术(Annotation Processing Tool)生成类文件用于存储path和activityClass的映射关系。在app进程启动的时候会拿到这些类文件,把里面存储的映射关系数据读到内存里,保存在路由表map中。 在进行路由跳转时,通过ARouter的build()方法传入要到达页面的路由地址,ARouter在路由表中找到路由地址对应的activityClass,然后new Intent(),通过ARouter的withString()方法传入携带参数,内部调用intent.putExtra(),通过ARouter的navigation()跳转,内部调用startActivity(intent)。
ARouter的基本使用
1. 添加依赖
在project下的<build.gradle>中添加ARouter的依赖
kt
android {
...
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
...
}
dependencies {
implementation 'androidx.annotation:annotation:1.3.0'
implementation 'com.alibaba:arouter-api:1.5.2'
annotationProcessor 'com.alibaba:arouter-compiler:1.5.2'
...
}
- 导入依赖遇到的问题
Q1: 导入ARouter依赖同步成功,编译时会报错;
kt
Manifest merger failed with multiple errors, see logs
A1: 在project中的<gradle.properties>中添加配置,将项目迁移到AndroidX中;
kt
android.useAndroidX=true //在编译时自动使用AndroidX库替代旧的Support库
android.enableJetifier=true //自动将项目中使用的第三方库迁移到AndroidX
Q2: 添加javaCompileOptions配置会报错;
kt
ARouter::Compiler >>> No module name, for more information, look at gradle log.
A2: arguments中键名不对,将moduleName 改为AROUTER_MODULE_NAME就可以编译成功了;
kt
原:
javaCompileOptions {
annotationProcessorOptions {
arguments = [moduleName : project.getName()]
}
}
改:
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
2. 初始化ARouter
官方建议在Application里面进行ARouter的初始化,并将初始化ARouter的Application配置到Androidmanifest.xml文件中;
- 创建初始化ARouter的Application类<InitApplication.kt>;
kt
class InitApplication : Application() {
private var isDebugARouter = true
override fun onCreate() {
super.onCreate()
if (isDebugARouter){
// 写在init之前才会生效
ARouter.openLog()
ARouter.openDebug()
}
//初始化ARouter
ARouter.init(this)
}
}
- 在Androidmanifest中配置InitApplication ;
xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.arouterdemo">
<application
android:name="com.example.arouterdemo.InitApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.ARouterDemo">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
3. ARouter的使用
这里先简要介绍下ARouter的使用流程,由于ARouter主要用于解决组件间和模块间的界面跳转问题,在接下来的4. ARouter模块跳转中会以模块为单位进行跳转。
kt
1. 在即将跳转到的Activity注解中指明该Activity的路径
【 @Route(path = "/xxx/xxx") 】:
@Route(path = "/main/first")
class FirstActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_first)
}
}
2. 在主动跳转的Activity中指明要跳转去的Activity的路径:
【 ARouter.getInstance().build("/xxx/xxx").navigation() 】
class SecondActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
initView()
}
fun initView(){
btn.setOnClickListener {
ARouter.getInstance().build("/main/first").navigation()
}
}
}
- 遇到的问题
**Q:**ARouter简单使用的apk点击跳转按钮提示没有匹配路径;
kt
ARouter::: ARouter::There is no route match the path [/xxx/xxx], in group [xxx][ ]
**A:**由于Android开发使用的是kotlin语言编写的,因此需要使用kapt依赖,而ARouter使用的是annotationProcessor;
kt
原:
android {
...
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
...
}
dependencies {
implementation 'androidx.annotation:annotation:1.3.0'
implementation 'com.alibaba:arouter-api:1.5.2'
annotationProcessor 'com.alibaba:arouter-compiler:1.5.2'
...
}
改:
apply plugin: 'kotlin-kapt'
android {
...
defaultConfig {
...
kapt {
arguments {
arg("AROUTER_MODULE_NAME", project.getName())
}
}
}
...
}
dependencies {
implementation 'androidx.annotation:annotation:1.3.0'
implementation 'com.alibaba:arouter-api:1.5.2'
kapt 'com.alibaba:arouter-compiler:1.5.2'
...
}
4. ARouter模块跳转
- 创建模块
首先创建一个Base模块用来存放项目模块的路由路径;
project根目录 ---> new ---> module ---> Android Library ---> 设置module名称 ---> Next ---> Finish
-
导入ARouter依赖
只要是使用ARouter的所有模块,都需要在module的build.gradle中添加ARouter依赖;
-
创建路由路径对象
根据实际项目创建的模块来编写路由路径,路径至少2级,不同模块的1级路径不相同,写成一样的可能会出问题;
kt
object RouterPath {
// 主页面
const val MAIN = "/app/main"
// 设置主页
const val SETTING = "/setting/main"
// 消息页面
const val MESSAGE = "/message/main"
}
- 根据路由路径封装跳转api
kt
object Router {
fun navMain(){
ARouter.getInstance().build(RouterPath.MAIN).navigation()
}
fun navSetting(){
ARouter.getInstance().build(RouterPath.SETTING).navigation()
}
fun navMessage(){
ARouter.getInstance().build(RouterPath.MESSAGE).navigation()
}
}
- 模块间依赖
由于module-base模块中有整个项目的路由路径及跳转api,属于项目公用的模块,所以其他所有模块都需要在build.gradle中依赖module-base模块;
kt
以app模块为例:
dependencies {
...
implementation project(path: ':module-base')
implementation project(path: ':module-setting')
implementation project(path: ':module-message')
}
以上仅为公用Base模块的创建,除此之外还创建了module-message和module-setting模块用来测试模块间跳转,流程相同,①创建模块,②导入ARouter依赖,③创建主Activity。
- module-message模块的代码
在页面上添加两个按钮,分别用来点击跳转到另外两个模块主页面,在点击监听中添加ARouter跳转逻辑即可(三个模块的代码几乎一样,区别在于主模块app导入其他所有模块的依赖,而其他模块只需要导入module-base模块的依赖);
xml
activity_message.xml
<?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=".MessageActivity">
<Button
android:id="@+id/to_setting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="setting"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<Button
android:id="@+id/to_main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="main"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Message Activity"
android:textSize="50dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
kt
MessageActivity.kt
@Route(path = RouterPath.MESSAGE)
class MessageActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_message)
initView()
}
private fun initView(){
to_setting.setOnClickListener {
Router.navSetting()
}
to_main.setOnClickListener {
Router.navMain()
}
}
}
使用ARouter实现Fragment跳转
部分项目中使用的是该方法,但我没有写demo,大致流程如下:
- 创建Fragment的Route<MainRoute.kt>
kt
object MainRoute {
const val FRAGMENT_A = "/app/afragment"
const val FRAGMENT_B = "/app/bfragment"
const val FRAGMENT_C = "/app/cfragment"
}
- 创建导航图<MainNavGraph.kt>
这段代码有问题,不知道是否因为导入的navigation版本问题,这里先讲实现思路;
kt
object MainNavGraph {
fun create(navController: NavController, startDest : String = MainRoute.FRAGMENT_A){
navController.apply{
graph = createGraph(
route = "app",
startDestination = startDest
){
// 创建Fragment实例,并将其添加到MainRoute路由中
fragment<AFragment>(route = MainRoute.FRAGMENT_A)
fragment<BFragment>(route = MainRoute.FRAGMENT_B)
fragment<CFragment>(route = MainRoute.FRAGMENT_C)
}
}
}
}
导航图主要是把fragment和route路由路径对应起来,当根据路由路径进行跳转时,实际上是跳转到对应的Fragment中,而Fragment就不需要添加route注释@Route来绑定路由路径了。
- Fragment跳转实现
在Fragment所在的Activity中需要绑定NavHostFragment和navController,然后通过navController和route路径来实现跳转;
java
class MainActivity : AppCompatActivity() {
private lateinit var navController : NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navHostFragment = supportFragmentManager.findFragmentById(R.id.fragment_view) as NavHostFragment
navController = navHostFragment.navController
MainNavGraph.create(navController)
to_setting.setOnClickListener {
navController.navigate(MainRoute.FRAGMENT_B)
}
}
}
👀关注公众号:Android老皮!!!欢迎大家来找我探讨交流👀