上一篇:[Android多回退栈实践(一)]
在上一篇文章中,我们介绍了Android中的多回退栈,并使用FragmentManager
实现了最朴素的多回退栈用例。接下来,我们将借助Android的Navigation组件,更加方便的实现多回退栈。
已知我们已经有6个页面:
Music,Favorite,Collection,MusicDetail,FavoriteDetail,CollectionDetail。
引入Jetpack Navigation之后,我们构建一个Graph:
<?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_multi_stack"
app:startDestination="@id/music">
<navigation
android:id="@+id/music"
app:startDestination="@+id/music_main">
<fragment
android:id="@+id/music_main"
android:name="xxx.MusicFragment"
android:label="MusicFragment">
<action
android:id="@+id/action_music_to_detail"
app:destination="@id/music_detail" />
</fragment>
<fragment
android:id="@+id/music_detail"
android:name="xxx.MusicDetailFragment"
android:label="MusicDetailFragment" />
</navigation>
<navigation
android:id="@+id/favorite"
app:startDestination="@+id/favorite_main">
<fragment
android:id="@+id/favorite_main"
android:name="xxx.FavoriteFragment"
android:label="FavoriteFragment">
<action
android:id="@+id/action_favorite_to_detail"
app:destination="@id/favorite_detail" />
</fragment>
<fragment
android:id="@+id/favorite_detail"
android:name="xxx.FavoriteDetailFragment"
android:label="FavoriteDetailFragment" />
</navigation>
<navigation
android:id="@+id/collection"
app:startDestination="@+id/collection_main">
<fragment
android:id="@+id/collection_main"
android:name="xxx.CollectionFragment"
android:label="CollectionFragment">
<action
android:id="@+id/action_collection_to_detail"
app:destination="@id/collection_detail" />
</fragment>
<fragment
android:id="@+id/collection_detail"
android:name="xxx.CollectionDetailFragment"
android:label="CollectionDetailFragment" />
</navigation>
</navigation>
我们仔细看下这里面的细节,我们构建的一个大的Graph中,其实包含了三个嵌套的Graph,里面分别有不同的跳转Action。
Music->MusicDetail,Favorite->FavoriteDetail,Collection->CollectionDetail。
我们修改一下_Music->MusicDetail_的跳转代码,剩余两个同理,也需要修改:
root.thing.setOnClickListener {
findNavController().navigate(R.id.action_music_to_detail)
}
此时,我们的主Fragment
跳转到详情Fragment
的Action就已经使用Navigation套件的跳转方法了。
现在,我们来配置主页。
主页的xml布局:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0px"
android:layout_height="0px"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@id/bottom_nav"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/nav_multi_stack" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_nav"
style="@style/Widget.MaterialComponents.BottomNavigationView.Colored"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:menu="@menu/bottom_navigation_multistack" />
</androidx.constraintlayout.widget.ConstraintLayout>
更改后的主页代码:
class MultiStackPage : AppCompatActivity() {
private var mSelectId = -1
private val controller by lazy { (supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment).navController }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_multi_stack)
findViewById<BottomNavigationView>(R.id.bottom_nav).let { nav ->
nav.setOnItemSelectedListener {
if (mSelectId == it.itemId) {
return@setOnItemSelectedListener true
}
when (it.itemId) {
R.id.action_music -> {
controller.navigate(
R.id.music, null, NavOptions.Builder().setLaunchSingleTop(true).setRestoreState(true).setPopUpTo(
controller.graph.findStartDestination().id, inclusive = false, saveState = true
).build()
)
}
R.id.action_favorite -> {
controller.navigate(
R.id.favorite, null, NavOptions.Builder().setLaunchSingleTop(true).setRestoreState(true).setPopUpTo(
controller.graph.findStartDestination().id, inclusive = false, saveState = true
).build()
)
}
R.id.action_collection -> {
controller.navigate(
R.id.collection, null, NavOptions.Builder().setLaunchSingleTop(true).setRestoreState(true).setPopUpTo(
controller.graph.findStartDestination().id, inclusive = false, saveState = true
).build()
)
}
}
mSelectId = it.itemId
true
}
nav.selectedItemId = R.id.action_music
}
}
}
其实非常简单,我们着重看一个跳转是如何处理的:
controller.navigate(
R.id.music, null, NavOptions.Builder().setLaunchSingleTop(true).setRestoreState(true).setPopUpTo(
controller.graph.findStartDestination().id, inclusive = false, saveState = true
).build()
)
在点击BottomNavigationView
的item
的时候,我们使用NavControler.navigate
进行跳转,第一个参数R.id.music
代表我们要跳转的嵌套图,我们现在有三个嵌套图,点击第一个_Music_的时候,跳转到_Music_的嵌套图,当然,我们还需要配置后面的NavOptions
:
setLaunchSingleTop
如果为true
,表示如果我们重新回到当前的item
,该item
放置在顶部,同时也可以避免多份拷贝。setRestoreState
如其函数名,表示恢复保存的状态到回退栈。setPopUpTo
,表示当前回退栈退栈,同时saveState = true
保存当前弹出的操作。
我们在上一篇文章中,已经知道了restoreState
,以及saveState
的用法,这里不再赘述。
配置完成NavOptions
之后,我们就可以进行正常的跳转了。
此时,我们就已经完成借助Navigation
实现了多回退栈。
可能大部分读者会认为,这也太麻烦了。有没有更简单一点的方法实现?当然有,Android的Jetpack Navigation组件中,提供了一个androidx.navigation:navigation-ui-ktx
的依赖,我们引入这个依赖,能更快速的实现BottomNavigationView
与Fragment
的多回退栈策略。步骤如下:
-
更改
<?xml version="1.0" encoding="utf-8"?>Menu
文件的实现,id
需要和Navigation的Graph中图的id
保持一致。``` -
修改主页的代码,只需要一步:```
findViewById(R.id.bottom_nav).let { nav ->
nav.setupWithNavController(controller)
}
完毕。
此时,我们就借助NavigationUI实现了多回退栈,很方便,不是么?我们不需要编写额外的代码,只需要在定义Menu
时注意Menu Item
的id
即可,NavigationUI早已为我们准备好了一切。
以上,我们便完成了关于Android多回退栈的所有实践方法,总结一下,有如下三种方法可供选择:
- 使用
FragmentManager
手动控制回退栈,重点接口是saveBackStack
,restoreBackStack
。 - 借助于Navigation,使用
NavControler.navigate
控制回退栈。 - 借助于NavigationUI,解放双手,无需做额外的管理。
在具体项目中,我们更推荐后两种方法,简单,高效。
祝各位好运!
文末
要想成为架构师,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。
如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。

一、架构师筑基必备技能
1、深入理解Java泛型
2、注解深入浅出
3、并发编程
4、数据传输与序列化
5、Java虚拟机原理
6、高效IO
……
二、Android百大框架源码解析
1.Retrofit 2.0源码解析
2.Okhttp3源码解析
3.ButterKnife源码解析
4.MPAndroidChart 源码解析
5.Glide源码解析
6.Leakcanary 源码解析
7.Universal-lmage-Loader源码解析
8.EventBus 3.0源码解析
9.zxing源码分析
10.Picasso源码解析
11.LottieAndroid使用详解及源码解析
12.Fresco 源码分析——图片加载流程
三、Android性能优化实战解析
- 腾讯Bugly:对字符串匹配算法的一点理解
- 爱奇艺:安卓APP崩溃捕获方案——xCrash
- 字节跳动:深入理解Gradle框架之一:Plugin, Extension, buildSrc
- 百度APP技术:Android H5首屏优化实践
- 支付宝客户端架构解析:Android 客户端启动速度优化之「垃圾回收」
- 携程:从智行 Android 项目看组件化架构实践
- 网易新闻构建优化:如何让你的构建速度“势如闪电”?
- …
四、高级kotlin强化实战
1、Kotlin入门教程
2、Kotlin 实战避坑指南
3、项目实战《Kotlin Jetpack 实战》
-
从一个膜拜大神的 Demo 开始
-
Kotlin 写 Gradle 脚本是一种什么体验?
-
Kotlin 编程的三重境界
-
Kotlin 高阶函数
-
Kotlin 泛型
-
Kotlin 扩展
-
Kotlin 委托
-
协程“不为人知”的调试技巧
-
图解协程:suspend
五、Android高级UI开源框架进阶解密
1.SmartRefreshLayout的使用
2.Android之PullToRefresh控件源码解析
3.Android-PullToRefresh下拉刷新库基本用法
4.LoadSir-高效易用的加载反馈页管理框架
5.Android通用LoadingView加载框架详解
6.MPAndroidChart实现LineChart(折线图)
7.hellocharts-android使用指南
8.SmartTable使用指南
9.开源项目android-uitableview介绍
10.ExcelPanel 使用指南
11.Android开源项目SlidingMenu深切解析
12.MaterialDrawer使用指南
六、NDK模块开发
1、NDK 模块开发
2、JNI 模块
3、Native 开发工具
4、Linux 编程
5、底层图片处理
6、音视频开发
7、机器学习
七、Flutter技术进阶
1、Flutter跨平台开发概述
2、Windows中Flutter开发环境搭建
3、编写你的第一个Flutter APP
4、Flutter开发环境搭建和调试
5、Dart语法篇之基础语法(一)
6、Dart语法篇之集合的使用与源码解析(二)
7、Dart语法篇之集合操作符函数与源码分析(三)
…
八、微信小程序开发
1、小程序概述及入门
2、小程序UI开发
3、API操作
4、购物商场项目实战……
全套视频资料:
一、面试合集
二、源码解析合集
三、开源框架合集
欢迎大家一键三连支持,若需要文中资料,直接点击文末优快云官方认证微信卡片免费领取【保证100%免费】↓↓↓