Android multiple back stacks导航的几种实现
Android multiple back stacks导航
谈谈android中多栈导航的几种实现.
什么是multiple stacks
当用户在app里切换页面时, 会需要向后回退到上一个页面, 页面历史被保存在一个栈里.
在Android里我们经常说"back stack".
有时候在app里我们需要维护多个back stack, 比较典型的场景是bottom navigation bar或者侧边的drawer.
如果需求要求在切换tab的时候保存每个tab上的历史, 这样当用户返回的时候还是返回到上次离开的地方, 这种就叫multiple stacks.
(与之对应的single stack行为是返回之后回到了tab首页.)
本文之后的内容都以bottom bar的多栈导航为例.
multi-stack的需求
首先还是讨论一下需求.
当bottom bar不支持多栈时, 当点击切换底部tab, 再返回原来的tab, 所有在之上打开的页面都会消失, 只有第一层(根)页面会显示.
这也是可以接受的, 甚至在material design里面作为Android平台的默认行为被提及: material design
但它同时也说了, 如果需要的话, 这个行为是可以被改的.
如果你想保留用户在上个tab看过的内容状态, 很可能就需要做multi-stack, 每个tab上的栈是独立退出, 分别保留的.
通常, 这还不是仅有的需求.
如果用户点击已选中的tab, 需要重置这个stack吗?
需要定制转场动画吗?
需要保留tab历史吗? 比如从tab A -> B -> C, 在C的根页面back, 是想回到B还是回到home tab?
在bottom navigation的默认实现中(用Android Studio创建一个Bottom Navigation的新项目), 在非home tab的根节点, 点击back, 总是先回到home tab, 再次back才会退出app.
因为这样是符合固定start destination的原则的. 用户在打开后和关闭前, 看到的是同一个页面.
但是如果你有保存tab历史的需求, 也可以考虑如何定制它.
当你更进一步地涉及到实现层面, 你会遇到更多实际操作的问题, 比如怎么把一个详情页push到一个指定的栈, 如何pop destination.
让我们列一下几个需求点:
- 维护多个栈.
- 切换tab: 手动点击tab或者其他tab内的交互. 比如dashboard跳转到某个内容tab.
- Push/pop destinations.
- 重选(reselect)tab会重置该栈. (clear history.)
- 转场动画
- tab历史.
技术背景
要进行导航的选型, 首先确定一下你的"destination"是什么.
是composable还是fragment, 或者干脆是View, 解决方案可能有很大的不同.
以这篇文章的scope来说, 我们就关注一个传统的android app, 用Activity和Fragment实现.
所以bottom tab上的tab内容, 是不同Fragment.
Fragment lifecycle
为什么这里要提一下Fragment的生命周期呢?
因为fragment的生命周期和它的ViewModel紧密关联, 进一步关系到了在导航过程中我们是否需要关注fragment的状态恢复和刷新.
首先复习一下Fragment生命周期的回调: 什么时候onDestroy
会被调用?
- 当
replace
transaction没有addToBackStack()
. - 当fragment被removed或者被
popBackStack()
.
当replace
transaction加上addToBackStack()
, 旧的fragment会被压入栈, 但它的生命周期只调用到onDestroyView().
当在它之上的其他fragment pop出来以后, 旧的这个fragment实例依然是同一个, 它重新显示, 重新从onCreateView()
开始走.
这是我们在single back stack下预期的行为.
ViewModel的生命周期和Fragment是对齐的, 也即Fragment的onDestroy()
调用时, ViewModel的onCleared()
被调用.
在导航切换目的地时, 如果fragment被destroy了, 我们可以保存一些关注的变量在saved instance bundle或者SavedStateHandle
里, 用于之后的状态恢复.
但是如果fragment没有被destroy, 我们可以剩下不少力气做这些状态恢复.
所以理想的状态是, 压栈后的fragment实例不会被销毁重建.
Navigation库/可能的方案
为了比较不同的解决方案, 我把一些sample放在了一起: https://github.com/mengdd/bottom-navigation-samples
Jetpack navigation component
官网: https://developer.android.com/guide/navigation
即便在FragmentManager的文档 里, 也建议开发者使用jetpack的navigation library来处理app的navigation.
multiple back stack的支持是Navigation 2.4.0-alpha01 和 Fragment 1.4.0-alpha01才加的.
试了下这个 demo,
代码非常简单, 我们基本什么都不用做.
关于这里面的思想可以看这篇文章: https://medium.com/androiddevelopers/multiple-back-stacks-b714d974f134
优点:
- 最知名, 毕竟是官方的库.
- 支持类型安全的参数.
- NavigationController支持pop到一个指定的destination.
- 可以和Compose navigation库一起使用.
缺点:
- Multi-stack的支持: 当切换tab时, 前一个tab上的所有fragment都会被destroy, 当返回tab时栈内fragment会重建. 所以状态会丢, 页面可能会刷新.
- 每个tab都需要是一个内嵌的navigation graph, 如果有一些common的destination, 需要include到每个graph中去. xml的navigation文件感觉很像一个大块的样板代码.
FragmentManager
如果我们想做更多的定制, 我们可以考虑用FragmentManager的新APIs自己手动实现.
在文档中doc 介绍的:
FragmentManager
allows you to support multiple back stacks with thesaveBackStack()
andrestoreBackStack()
methods. These methods allow you to swap between back stacks by saving one back stack and restoring a different one.
这是navigation component实现中实现多栈导航使用的方法.
所以也可以解释为什么切tab的时候fragment都被销毁了.
saveBackStack()
works similarly to callingpopBackStack()
with the optionalname
parameter: the specified transaction and all transactions after it on the stack are popped.
The difference is thatsaveBackStack()
saves the state of all fragments in the popped transactions.
优点:
- 精细控制, 开发者获得更多控制, 也更明白到底是怎么回事.
- 如果我们当前项目没有采用任何navigation library, 都是手动跳转, 采用这种方法我们就不用考虑迁移navigation.
缺点:
- 要写很多fragment transaction的样板代码.
- 和navigation components一样: 多栈实现中在切换栈时, 在旧的tab上的Fragments会被销毁, 返回时全部重建.
Enro
https://github.com/isaac-udy/Enro
对于多module的大型项目来说, 我很推荐这个库, 它可以帮助我们解耦module间的依赖.
multi-stack的demo
优点:
- 基于注解, 所以要写的代码很少, 导航使用很方便.
- 多module项目解耦.
- 传类型安全的参数和返回结果都很容易.
- 可以在ViewModel中获取navigation handle, 获取参数.
- 支持Compose做节点.
- 对Unit Test也有一个辅助测试的依赖.
- multi-stack support: 保持了切换tab的时候fragment实例.
缺点:
- 可能目前还不是很知名. 需要说服别人学和采用这个.
- Fragment的multi-stack: 不能rest stack到根节点. (尝试了一下定制这个行为, 有点难).
Simple-stack
https://github.com/Zhuinden/simple-stack
这里推荐一下这个库作者的文章Creating a BottomNavigation Multi-Stack using child Fragments with Simple-Stack.
关于如何用simple-stack来做multi-stack.
最开始作者展示了一个不用任何库, 仅用child fragments来实现的版本.
这是手动实现的另一种思想了.
后来才引入了用simple-stack做的demo
这是采用了原作者提供的sample, 比较简单, 试了一下以后我发现可能还需要添加更多的代码, 来做实际的应用.
比如详情页需要获得某个tab的local stack的实例, 从而把自己push上去.
优点:
- 作者在社区十分活跃, 有很多视频和文章介绍simple-stack这个库. 所以社区支持挺好.
- multi-stack support: 保持了切换tab的时候fragment实例.
- 支持控制和清空栈的历史.
- 有compose的扩展.
缺点:
- 如果你的bottom bar当前是在activity的布局里, 你需要把bottom bar和相关的东西都挪进一个RootFragment, 作为总的节点.
- 作者提供的multi-stack sample还非常简单, 需要写更多的代码来或者当前正确的栈来做push和pop操作. 不了解这个库可能会写得很丑.
其他库
还有一些库, 不是通用的navigation解决方案, 而只是为多栈导航设计的小库.
比如:
- https://github.com/DimaKron/Android-MultiStacks
- https://github.com/JetradarMobile/android-multibackstack
这些库都自带sample.
优点:
- 实现简单, 只用几个类. 如果我们想定制我们可以用这个代码.
- 要改动的范围可以限制在bottom navigation的部分, 而不是整体改变navigation方案.
缺点:
- 这些库都不是很出名, 有不再维护的风险.
- 可能和其他的navigation方案不能兼容, 比如Navigation Components. 需要考虑整体.
总结
android (fragment实现) multi-stack navigation的可能解决方案:
方案 | 流行 | 整体方案 | 活跃 | 支持清空栈 | Fragment被保存, 不被销毁 | 支持Multi-modules | Compose扩展 |
---|---|---|---|---|---|---|---|
Jetpack Navigation Components | 官方, 最出名 | Yes | Yes | Yes | No | Yes | Yes |
Fragment Manager | Android SDK | - | Yes | Yes | No | No | - |
Enro | Star: 188 | Yes | Yes | No | Yes | Yes | Yes |
Simple Stack | Star: 1.2k | Yes | Yes | Yes | Yes | Yes | Yes |
Child Fragments | Android SDK | - | Yes | Yes | Yes | No | - |
JetradarMobile/android-multibackstack | Star: 224 | No | No | Yes | No | No | - |
DimaKron/Android-MultiStacks | Star: 32 | No | Not sure | Yes | Yes | No | - |
注意:
- 整体方案: 表示该方案可以用于app整体的navigation解决方案, 而不仅仅是解决multi-stack的问题.
- Fragment被保存, 不被销毁: 当跳转或者切tab时, 被压入栈中的fragments不会被destroyed. 多栈支持的情况下, 尽管fragment被返回时都会被重建, 但是如果它不被销毁, 我们就不需要做额外的工作来缓存状态.
References:
- Sample: https://github.com/mengdd/bottom-navigation-samples
- Jetpack Navigation Components
- Enro
- Simple-stack
Android multiple back stacks导航的几种实现的更多相关文章
- Android tab导航的几种方法:ActionBar tab +fragment,Viewpager+pagerTitleStrip,开源框架ViewPageIndicator 和 ViewPager
action来实现tab标签 并跟fragment结合 因为要写新闻客户端这个tab导航是必须的 这里我写几个小练习,希望大家融会贯通. 1actionbar设置tab +fragment 布局是个l ...
- Android 抽屉效果的导航菜单实现
Android 抽屉效果的导航菜单实现 抽屉效果的导航菜单 看了很多应用,觉得这种侧滑的抽屉效果的菜单很好. 不用切换到另一个页面,也不用去按菜单的硬件按钮,直接在界面上一个按钮点击,菜单就滑出来,而 ...
- [置顶]
xamarin android Fragment实现底部导航栏
前段时间写了篇关于Fragment的文章,介绍了基础的概念,用静态和动态的方式加载Fragment Xamarin Android Fragment的两种加载方式.下面的这个例子介绍xamarin ...
- Android实现下拉导航选择菜单效果
本文介绍在Android中如何实现下拉导航选择菜单效果. 关于下拉导航选择菜单效果在新闻客户端中用的比较多,当然也可以用在其他的项目中,这样可以很方便的选择更多的菜单.我们可以让我们的应用顶部有左 ...
- Android隐藏状态栏、导航栏
Android隐藏状态栏.导航栏 private void hideStatusNavigationBar(){ if(Build.VERSION.SDK_INT<16){ this.getWi ...
- Android一个ListView列表之中插入两种不同的数据
http://www.cnblogs.com/roucheng/ Android一个ListView列表之中插入两种不同的数据 代码如下: public class ViewHolder{ Butto ...
- Android获取APK包名的几种方法
Android获取APK包名的几种方法:1.adb shell pm list package -f | findstr 关键字 #只能获取到包名,主Activity名无法获取到 2.使用aapt-- ...
- NFC(3)Android上的NFC,开启NFC,3种NDEF数据
Android对NFC技术的支持 Android2.3.1(API Level = 9)开始支持NFC技术,但Android2.x和Android3.x对NFC的支持非常有限.而从Android4.0 ...
- android使用tabhost实现导航
参考 http://blog.csdn.net/xixinyan/article/details/6771341 http://blog.sina.com.cn/s/blog_6b04c8eb0101 ...
随机推荐
- ArrayList扩容问题
今天上午上课在看JavaSE的面经,其中有问关于ArrayList和LinkedList的区别,就突然思考到,既然ArrayList是采用数组形式存储数据,对比我们自己使用到的数组,为什么ArrayL ...
- 关于Vue.cli 脚手架环境中引入Bootstrap时,table表格样式缺失的解决办法
Vue+bootstrap不能正常使用table的样式 环境:下载官网的本地bootstrap包,然后在vue 的index.html引入bootstrap的css和js环境 问题描述:1. vue里 ...
- synchronized有几种用法?
在 Java 语言中,保证线程安全性的主要手段是加锁,而 Java 中的锁主要有两种:synchronized 和 Lock,我们今天重点来看一下 synchronized 的几种用法. 用法简介 使 ...
- ArcGIS使用技巧(五)——批量裁剪
新手,若有错误还请指正! 最近用到了,所以记下来,用同一矢量范围裁剪多幅栅格数据.用到了ArcGIS中的迭代模型(图1): 图 1 首先,需要做一个准备工作,就是把需要裁剪的栅格数据放在同一数据库中( ...
- C#二次开发BIMFACE系列61 File Management文件管理服务接口二次开发及实战详解
系列目录 [已更新最新开发文章,点击查看详细] 在我的博客<C#二次开发BIMFACE系列61 File Management文件管理服务接口二次开发及实战详解>最后列出了 Fil ...
- Day 005:PAT练习--1047. 编程团体赛(20)
编程团体赛的规则为:每个参赛队由若干队员组成:所有队员独立比赛:参赛队的成绩为所有队员的成绩和:成绩最高的队获胜.现给定所有队员的比赛成绩,请你编写程序找出冠军队. 输入格式: 输入第一行给出一个正整 ...
- RxJava + Retrofit源码解析
RxJava + Retrofit怎么请求网络,具体的用法这里就不讲了,本文只讲一些重点源码. 版本如下: okhttp : "com.squareup.okhttp3:okhttp:3.1 ...
- 手写useState与useEffect
手写useState与useEffect useState与useEffect是驱动React hooks运行的基础,useState用于管理状态,useEffect用以处理副作用,通过手写简单的us ...
- drools的简单入门案例
一.背景 最近在学习规则引擎drools,此处简单记录一下drools的入门案例. 二.为什么要学习drools 假设我们存在如下场景: 在我们到商店购买衣服的时候,经常会发生这样的事情,购买1件不打 ...
- 通过Go实现AES加密和解密工具
本文包含如下两个内容: AES加密介绍及实现原理 Go实现AES加密和解密工具 AES加密介绍及实现原理 AES( advanced encryption standard)使用相同密钥进行加密和解密 ...