我對 Android 的狀態欄和導航欄一直有種情結,在我作 Android 開發以前,我就喜歡經過一些 Xposed 插件來讓狀態欄和導航欄變色或者透明,以消除那醜醜的兩個黑條。java
作 Android 開發以後,我更是寫了兩篇文章(透明狀態欄和導航欄的終極解決方案 、Android 狀態欄和導航欄的真終極解決方案 )來分析 Android 4.4 以上的狀態欄和導航欄的各類效果的實現,還開源了一個庫 UltimateBar。git
可是隨着本身技術的成長,我愈加以爲這個庫設計的很差,存在不少缺陷,以致於到了維護不動的境地。好比,常常有人給我提這樣的 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 的參數,保證以前設置過的效果不受影響,可是這樣操做起來太過繁瑣,我選擇了另外一種很簡單的方法。
其實方法很簡單,當第一次設置狀態欄或導航欄的時候,無論須要什麼效果,都讓佈局侵入到狀態欄和導航欄,而後根據要不要侵入來設置 decorView
的 topPadding
和 bottomPadding
,好比上面提到的需求,就能夠在剛進入 Activity 的時候,就讓佈局同時侵入到狀態欄和導航欄,而後給 decorView
設置一個狀態欄高度的 topPadding
,看起來效果就是佈局沒有侵入到狀態欄了,當點擊按鈕須要讓狀態欄被侵入的時候,只需再把 decorView
的 topPadding
設爲0,而不用再管導航欄,也不用保存導航欄以前設置的狀態了,簡單粗暴。
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 模式也須要調用 decorView
的 setSystemUiVisibility
方法,這就意味着 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,也歡迎跟我交流