Android 狀態欄和導航欄的終極解決方案 最終版

緣起

我對 Android 的狀態欄和導航欄一直有種情結,在我作 Android 開發以前,我就喜歡經過一些 Xposed 插件來讓狀態欄和導航欄變色或者透明,以消除那醜醜的兩個黑條。java

作 Android 開發以後,我更是寫了兩篇文章(透明狀態欄和導航欄的終極解決方案 Android 狀態欄和導航欄的真終極解決方案 )來分析 Android 4.4 以上的狀態欄和導航欄的各類效果的實現,還開源了一個庫 UltimateBargit

可是隨着本身技術的成長,我愈加以爲這個庫設計的很差,存在不少缺陷,以致於到了維護不動的境地。好比,常常有人給我提這樣的 issues:github

這兩個問題,實際上是同一個問題,主要就是由於狀態欄和導航欄的設置耦合到了一塊兒,致使修改了狀態欄,導航欄也會收到影響,固然,我不是故意要把他們耦合到一塊兒的,只是要實現不少複雜的功能不得已而爲之,不過如今我已經完全解決這個問題了,這個後面會說到。app

基於這些緣由,我最近終於又開發了一個新的庫 UltimateBarX,這個名字借鑑了 Google 爸爸的「AndroidX」,Google 以前的 Support 庫太混亂了,爲了統一,新開了一個「AndroidX」,我起這個名字,多少有點類似的意味。工具

接下拉就言歸正傳,看看這個庫的用法以及它的實現原理。佈局

fitsSystemWindows 方法提及

View 裏面有個方法,叫setFitsSystemWindows,這個方法有什麼用呢,當給 Activity 設置全屏的時候,若是給 contentView 的最外層設置 setFitsSystemWindows(true),那麼 contentView 就不會侵入狀態欄和導航欄,不然,就會侵入。什麼意思呢?舉個例子,在 Android 5.0 以上,若是在 Activity 中作如下操做字體

int flag = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
        | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
        | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
getWindow().getDecorView().setSystemUiVisibility(flag);
getWindow().setStatusBarColor(Color.parseColor("#66000000"));
getWindow().setNavigationBarColor(Color.parseColor("#66000000"));
複製代碼

就會看到這樣的效果 gradle

若是再在後面加上ui

findViewById(R.id.contentView).setFitsSystemWindows(true);
複製代碼

效果就會變成這樣 this

以上兩個圖已經很形象的說明了侵入和非侵入的區別。如今來解釋一下爲何狀態欄和導航欄設置會耦合,從上面的兩個圖能夠看出 setFitsSystemWindows(true) 方法是對狀態欄和導航欄同時生效的,就是說要麼都侵入,要麼都不侵入,那麼問題來了,若是我如今要求佈局侵入到狀態欄而不侵入到導航欄要怎麼辦呢,其實也是能夠實現的,只須要在上面的代碼中把 View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 去掉,即

int flag = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
        | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
getWindow().getDecorView().setSystemUiVisibility(flag);
getWindow().setStatusBarColor(Color.parseColor("#66000000"));
findViewById(R.id.contentView).setFitsSystemWindows(false);
複製代碼

這樣就能達到只設置狀態欄,而導航欄不受影響的效果,一樣的,若是須要讓佈局只侵入導航欄而狀態欄不受影響,只須要

int flag = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
        | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
getWindow().getDecorView().setSystemUiVisibility(flag);
getWindow().setNavigationBarColor(Color.parseColor("#66000000"));
findViewById(R.id.contentView).setFitsSystemWindows(false);
複製代碼

這樣看來好像並無什麼問題,各類狀況都能解決,可是,若是如今需求是這樣的,首先剛進入 Activity 的時候,讓佈局只侵入到導航欄,而後當點擊某個按鈕的時候,導航欄保持被侵入不變,同時,讓佈局侵入到狀態欄,這時候若是還用上面的方法,就會致使導航欄被還原成初始狀態,如圖所示

這是由於在屢次調用 setSystemUiVisibility 方法時,只有最後一次纔是有效的,前面的調用都會被最後一次覆蓋掉,那要怎麼解決這個問題呢,有種方法就是保存當前 Activity 的狀態欄和導航欄的狀態,當每次須要設置的時候,都根據保存的狀態從新設置 setSystemUiVisibility 的參數,保證以前設置過的效果不受影響,可是這樣操做起來太過繁瑣,我選擇了另外一種很簡單的方法。

UltimateBarX 的核心原理

其實方法很簡單,當第一次設置狀態欄或導航欄的時候,無論須要什麼效果,都讓佈局侵入到狀態欄和導航欄,而後根據要不要侵入來設置 decorViewtopPaddingbottomPadding,好比上面提到的需求,就能夠在剛進入 Activity 的時候,就讓佈局同時侵入到狀態欄和導航欄,而後給 decorView 設置一個狀態欄高度的 topPadding,看起來效果就是佈局沒有侵入到狀態欄了,當點擊按鈕須要讓狀態欄被侵入的時候,只需再把 decorViewtopPadding 設爲0,而不用再管導航欄,也不用保存導航欄以前設置的狀態了,簡單粗暴。

UltimateBarX 的用法

UltimateBarX 的用法也很簡單,此次我花了很長的時間思考怎樣讓方法調用起來更簡單,同時往後維護起來更方便,首先在 build.gradle 中添加

dependencies {
    implementation 'com.zackratos.ultimatebarx:ultimatebarx:0.1.1'
}
複製代碼

若是須要設置狀態了,能夠在 Activity 中

UltimateBarX.create(UltimateBarX.STATUS_BAR)        // 設置狀態欄
    .fitWindow(true)                                // 佈局是否侵入狀態欄(true 不侵入,false 侵入) 
    .bgColor(Color.BLACK)                           // 狀態欄背景顏色(色值)
    .bgColorRes(R.color.deepSkyBlue)                // 狀態欄背景顏色(資源id)
    .bgRes(R.drawable.bg_gradient)                  // 狀態欄背景 drawable
    .light(false)                                   // light模式(狀態欄字體灰色 Android 6.0 以上支持)
    .apply(this);
複製代碼

方法很是簡單,註釋也寫的很清楚了,這裏有三個設置背景的方法,寫一個就好了,多寫也只有一個會生效,優先級 bgRes > bgColor > bgColorRes,若是須要設置導航欄,在 create 裏面傳入 UltimateBarX.NAVIGATION_BAR 便可,其餘不變,狀態欄和導航欄徹底獨立設置,互不影響,作到了真正的解耦。

若是要實現上面所說的需求,只須要在剛進入 Activity 的時候

UltimateBarX.create(UltimateBarX.NAVIGATION_BAR)
    .fitWindow(false)
    .bgColor(Color.parseColor("#66000000"))
    .apply(this);
複製代碼

設置佈局侵入到導航欄,而後點擊按鈕,讓佈局侵入到狀態欄,只需

UltimateBarX.create(UltimateBarX.STATUS_BAR)
    .fitWindow(false)
    .bgColor(Color.parseColor("#66000000"))
    .apply(this);
複製代碼

關於 light 方法

這裏面有個 light 方法,用來設置狀態欄或者導航欄的 light 模式,當 light 模式爲 true 時,狀態欄的字體會變灰,對應的導航欄的按鈕會變灰,這裏有個問題,就是設置 light 模式也須要調用 decorViewsetSystemUiVisibility 方法,這就意味着 light 模式是須要保存狀態的,若是不保存,第一次設置狀態欄爲 light 模式,第二次再設置導航欄的時候,狀態欄的 light 模式就會被清除,這個前面已經解釋過了。

那如何保存狀態呢,爲了使侵入性更低,能夠用一個單例對象來保存每一個 Activity 的狀態欄和導航欄的 light 狀態,這樣又會有一個問題,在 Activity 退出的時候,必須把當前 Activity 對象從單例裏面移除,不然會形成內存泄漏,那麼怎樣監聽 Activity 退出呢,經常使用的作法就是在 Activity 裏面添加一個看不見的 Fragment,當 Fragment 的 onDestroy 方法被調用的時候,說明 Activity 的 onDestroy 被調用了,即 Activity 已退出。

不過如今已經不用這麼麻煩了,Google 爸爸在 JetPack 組件裏面,給咱們提供了很是好用的工具「Lifecycle」,能夠無侵入的監聽 Activity 和 Fragment 的生命週期,這裏就使用 Lifecycle 來實現的,很是方便,其實 Lifecycle 的實現原理也是在內部添加一個 Fragment 來監聽的。

最後

原理和用法都講的差很少了,最後再貼一遍 UltimateBarX 地址,但願你們多多關注,多多 star、fork,提 issues、提 pr,也歡迎跟我交流

github.com/Zackratos/U…

相關文章
相關標籤/搜索