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
  • Timer是什麼 Timer 是一種用於創建定期粒度行為的機制。 與標準的 .NET System.Threading.Timer 類相似,Orleans 的 Timer 允許在一段時間後執行特定的操作,或者在特定的時間間隔內重覆執行操作。 它在分散式系統中具有重要作用,特別是在處理需要周期性執行的 ...
  • 前言 相信很多做WPF開發的小伙伴都遇到過表格類的需求,雖然現有的Grid控制項也能實現,但是使用起來的體驗感並不好,比如要實現一個Excel中的表格效果,估計你能想到的第一個方法就是套Border控制項,用這種方法你需要控制每個Border的邊框,並且在一堆Bordr中找到Grid.Row,Grid. ...
  • .NET C#程式啟動閃退,目錄導致的問題 這是第2次踩這個坑了,很小的編程細節,容易忽略,所以寫個博客,分享給大家。 1.第一次坑:是windows 系統把程式運行成服務,找不到配置文件,原因是以服務運行它的工作目錄是在C:\Windows\System32 2.本次坑:WPF桌面程式通過註冊表設 ...
  • 在分散式系統中,數據的持久化是至關重要的一環。 Orleans 7 引入了強大的持久化功能,使得在分散式環境下管理數據變得更加輕鬆和可靠。 本文將介紹什麼是 Orleans 7 的持久化,如何設置它以及相應的代碼示例。 什麼是 Orleans 7 的持久化? Orleans 7 的持久化是指將 Or ...
  • 前言 .NET Feature Management 是一個用於管理應用程式功能的庫,它可以幫助開發人員在應用程式中輕鬆地添加、移除和管理功能。使用 Feature Management,開發人員可以根據不同用戶、環境或其他條件來動態地控制應用程式中的功能。這使得開發人員可以更靈活地管理應用程式的功 ...
  • 在 WPF 應用程式中,拖放操作是實現用戶交互的重要組成部分。通過拖放操作,用戶可以輕鬆地將數據從一個位置移動到另一個位置,或者將控制項從一個容器移動到另一個容器。然而,WPF 中預設的拖放操作可能並不是那麼好用。為瞭解決這個問題,我們可以自定義一個 Panel 來實現更簡單的拖拽操作。 自定義 Pa ...
  • 在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式。 ...
  • 一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 1、自主搭建vue前端和. ...
  • Csharper中的表達式樹 這節課來瞭解一下表示式樹是什麼? 在C#中,表達式樹是一種數據結構,它可以表示一些代碼塊,如Lambda表達式或查詢表達式。表達式樹使你能夠查看和操作數據,就像你可以查看和操作代碼一樣。它們通常用於創建動態查詢和解析表達式。 一、認識表達式樹 為什麼要這樣說?它和委托有 ...
  • 在使用Django等框架來操作MySQL時,實際上底層還是通過Python來操作的,首先需要安裝一個驅動程式,在Python3中,驅動程式有多種選擇,比如有pymysql以及mysqlclient等。使用pip命令安裝mysqlclient失敗應如何解決? 安裝的python版本說明 機器同時安裝了 ...