Android multiple back stacks導航的幾種實現

来源:https://www.cnblogs.com/mengdd/archive/2022/06/25/solutions-of-android-multi-stack-navigation.html
-Advertisement-
Play Games

Android multiple back stacks導航 談談android中多棧導航的幾種實現. 什麼是multiple stacks 當用戶在app里切換頁面時, 會需要向後回退到上一個頁面, 頁面歷史被保存在一個棧里. 在Android里我們經常說"back stack". 有時候在app ...


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會被調用?

  • replacetransaction沒有addToBackStack().
  • 當fragment被removed或者被popBackStack().

replacetransaction加上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實例不會被銷毀重建.

為了比較不同的解決方案, 我把一些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-alpha01Fragment 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 the saveBackStack()
and restoreBackStack() 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 calling popBackStack() with the optional name
parameter: the specified transaction and all transactions after it on the stack are popped.
The difference is that saveBackStack() 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解決方案, 而只是為多棧導航設計的小庫.
比如:

這些庫都自帶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:

作者: 聖騎士Wind
出處: 博客園: 聖騎士Wind
Github: https://github.com/mengdd
微信公眾號: 聖騎士Wind
微信公眾號: 聖騎士Wind
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、簡介 vmware為我們提供了三種網路工作模式,它們分別是:Bridged(橋接模式)、NAT(網路地址轉換模式)、Host-Only(僅主機模式)。 查看網路連接 打開vmware虛擬機,我們可以在選項欄的“編輯”->“虛擬網路編輯器”中看到VMnet0(橋接模式)、VMnet1(僅主機模式) ...
  • 安裝docker後,提示需要啟用hyper-v,在控制面板中勾選Hyper-v,然後重啟,更新快完成就提示無法完成功能配置,正在撤銷更改 解決方法 方法1 控制面板一個一個選 方法2 百度了n多內容,命令行什麼的都試了下,但是都沒有起作用.有說禁用了服務,看了下,確實是禁用了,但是開起來後還是不行. ...
  • 這玩意搞了我今天,直接裂開!系統更新根本解決不了 好在查了相關資料才知道,原來微軟在 Win10 的更新中,將搜索功能和語音助手 Cortana 進行了拆分,搜索成了一個獨立的功能,還好有外媒發現問題原因是 Bing 和 Cortana 集成造成,而且修複很簡單。 方法1 使用電腦管家修複; 方法2 ...
  • 四、Nginx 4.1、概述 4.1.1、介紹 Nginx是一款輕量級的Web伺服器/反向代理伺服器/電子右鍵(IMAP/POP3)代理伺服器。其特點是占有記憶體少,併發能力強,事實上Nginx的併發能力在同類型的網頁伺服器中表現較好,中國大陸使用Nginx的網站有:百度、就京東、新浪、網易、騰訊、淘 ...
  • 鏡像下載、功能變數名稱解析、時間同步請點擊 阿裡雲開源鏡像站 CentOS 1.備份原來的源 在控制台輸入mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup備份原本的源 2、下載新的 CentOS-Bas ...
  • 鏡像下載、功能變數名稱解析、時間同步請點擊 阿裡雲開源鏡像站 一、環境要求 系統版本:CentOS7.x版本 硬體配置:記憶體2GB以上 cpu2核以上 硬碟大於30G 集群網路配置:集群中所有伺服器內網必須互通,並且需要訪問外網來拉取鏡像 禁用swap分區 二、k8s基礎環境操作: 1、關閉防火牆: [ro ...
  • 程式包編譯安裝的步驟: 源代碼-->預處理-->編譯-->彙編-->鏈接-->執行 多文件:文件中的代碼之間,很可能存在跨文件依賴關係 編譯源碼的項目工具 使用相關的項目管理工具可以大大減少編譯過程的複雜度 根據源碼類型來對這些工具進行分類: C、C++的源碼編譯:使用 make 項目管理器 con ...
  • 1、簡述 binlog 二進位日誌文件,這個文件記錄了MySQL所有的DML操作。通過binlog日誌我們可以做數據恢復,增量備份,主主複製和主從複製等等。 2、Docker中無法使用vim問題解決 https://blog.csdn.net/Tomwildboar/article/details/ ...
一周排行
    -Advertisement-
    Play Games
  • 1、什麼是MQTT? MQTT(Message Queuing Telemetry Transport,消息隊列遙測傳輸協議),是一種基於發佈/訂閱(publish/subscribe)模式的"輕量級"通訊協議,該協議構建於TCP/IP協議上,由IBM在1999年發佈。MQTT最大優點在於,可以以極 ...
  • 溫馨提示,請使用ctrl+F進行快速查找 ws2_32.lib error LNK2001: 無法解析的外部符號 __imp_htons error LNK2001: 無法解析的外部符號 __imp_ntohl error LNK2001: 無法解析的外部符號 __imp_ntohs error L ...
  • public static void GetRegistData() { string name = "huishuangzhu"; //搜索到註冊表根目錄 RegistryKey hkml = Registry.ClassesRoot; //搜索到註冊表根目錄下的XXX文件夾。 RegistryK ...
  • 作者:小牛呼嚕嚕 | https://xiaoniuhululu.com 電腦內功、JAVA源碼、職業成長、項目實戰、面試相關資料等更多精彩文章在公眾號「小牛呼嚕嚕」 前言 讀過筆者之前的一篇文章Java記憶體模型(JMM)詳解, 我們知道了由於電腦為了充分利用CPU的高性能,以及各個硬體 存取速 ...
  • 使用Apache PDFBox實現拆分、合併PDF 問題背景 如何拆分PDF? 如何合併PDF? 如何拆分併合並PDF實現去除PDF的某些頁? Apache PDFBox介紹 Apache PDFBox 1.8.10官方文檔 Apache PDFBox 庫是一個開源的 Java 工具,用於處理 PD ...
  • 問題:是不是虛函數的調用就一定是動態綁定?不是的 1:在類的構造函數當中,調用虛函數,也是靜態綁定(構造函數中對虛函數的調用不發生動態綁定) 2:如果不是通過指針或者引用變數來調用虛函數,那就是靜態綁定 代碼1 class Base { public: Base(int data=10):ma(da ...
  • 本篇文章我們將進一步探討下Guava Cache 實現層面的一些邏輯與設計策略,讓我們可以對Guava Cache整體有個更加明朗的認識,促進實際使用中對其的理解。 ...
  • JZ12 矩陣中的路徑 描述 請設計一個函數,用來判斷在一個n乘m的矩陣中是否存在一條包含某長度為len的字元串所有字元的路徑。路徑可以從矩陣中的任意一個格子開始,每一步可以在矩陣中向左,向右,向上,向下移動一個格子。如果一條路徑經過了矩陣中的某一個格子,則該路徑不能再進入該格子。 思路 我們看到他 ...
  • 抽象類和普通類有什麼區別? 抽象類一般不用作抽象實體類型 一般把上面類設計為抽象類? 基類 定義抽象類的唯一目的, 就是去建立派生類. 我們在抽象類基礎上要定義出功能各異的派生類, 再用這些派生類去建立對象. 凡是包含純虛函數的類都是抽象類. 純虛函數不用實現, 故不能被調用, 抽象類無法建立對象. ...
  • 打包下載成.zip文件 項目背景 公司使用vue + SpringBoot實現批量下載功能 今天在調試批量下載這個功能。打包成.zip文件時,在返回給前端瀏覽器出現報錯信息: 後端報錯: ERROR c.c.p.c.e.BusinessExceptionHandler - java.io.IOExc ...