2020-07-09

緣起

我對 Android 的狀態(tài)欄和導(dǎo)航欄一直有種情結(jié),在我做 Android 開發(fā)之前,我就喜歡通過一些 Xposed 插件來讓狀態(tài)欄和導(dǎo)航欄變色或者透明,以消除那丑丑的兩個黑條。

從 fitsSystemWindows 方法說起,View 里面有個方法,叫setFitsSystemWindows,這個方法有什么用呢,當(dāng)給 Activity 設(shè)置全屏的時候,如果給 contentView 的最外層設(shè)置 setFitsSystemWindows(true),那么 contentView 就不會侵入狀態(tài)欄和導(dǎo)航欄,否則,就會侵入。什么意思呢?舉個例子,在 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"));

就會看到這樣的效果

如果再在后面加上

findViewById(R.id.contentView).setFitsSystemWindows(true);

效果就會變成這樣

以上兩個圖已經(jīng)很形象的說明了侵入和非侵入的區(qū)別?,F(xiàn)在來解釋一下為什么狀態(tài)欄和導(dǎo)航欄設(shè)置會耦合,從上面的兩個圖可以看出setFitsSystemWindows(true)方法是對狀態(tài)欄和導(dǎo)航欄同時生效的,就是說要么都侵入,要么都不侵入,那么問題來了,如果我現(xiàn)在要求布局侵入到狀態(tài)欄而不侵入到導(dǎo)航欄要怎么辦呢,其實也是可以實現(xiàn)的,只需要在上面的代碼中把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);

這樣就能達(dá)到只設(shè)置狀態(tài)欄,而導(dǎo)航欄不受影響的效果,同樣的,如果需要讓布局只侵入導(dǎo)航欄而狀態(tài)欄不受影響,只需要

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);

這樣看來好像并沒有什么問題,各種情況都能解決,但是,如果現(xiàn)在需求是這樣的,首先剛進入 Activity 的時候,讓布局只侵入到導(dǎo)航欄,然后當(dāng)點擊某個按鈕的時候,導(dǎo)航欄保持被侵入不變,同時,讓布局侵入到狀態(tài)欄,這時候如果還用上面的方法,就會導(dǎo)致導(dǎo)航欄被還原成初始狀態(tài),如圖所示

這是因為在多次調(diào)用 setSystemUiVisibility 方法時,只有最后一次才是有效的,前面的調(diào)用都會被最后一次覆蓋掉,那要怎么解決這個問題呢,有種方法就是保存當(dāng)前 Activity 的狀態(tài)欄和導(dǎo)航欄的狀態(tài),當(dāng)每次需要設(shè)置的時候,都根據(jù)保存的狀態(tài)重新設(shè)置 setSystemUiVisibility 的參數(shù),保證之前設(shè)置過的效果不受影響,但是這樣操作起來太過繁瑣,我選擇了另一種很簡單的方法。

UltimateBarX 的核心原理

其實方法很簡單,當(dāng)?shù)谝淮卧O(shè)置狀態(tài)欄或?qū)Ш綑诘臅r候,不管需要什么效果,都讓布局侵入到狀態(tài)欄和導(dǎo)航欄,然后根據(jù)要不要侵入來設(shè)置decorView 的 topPadding 和 bottomPadding,比如上面提到的需求,就可以在剛進入 Activity 的時候,就讓布局同時侵入到狀態(tài)欄和導(dǎo)航欄,然后給decorView 設(shè)置一個狀態(tài)欄高度的 topPadding,看起來效果就是布局沒有侵入到狀態(tài)欄了,當(dāng)點擊按鈕需要讓狀態(tài)欄被侵入的時候,只需再把decorView 的 topPadding 設(shè)為0,而不用再管導(dǎo)航欄,也不用保存導(dǎo)航欄之前設(shè)置的狀態(tài)了,簡單粗暴。

UltimateBarX 的用法

UltimateBarX 的用法也很簡單,這次我花了很長的時間思考怎樣讓方法調(diào)用起來更簡單,同時日后維護起來更方便,首先在 build.gradle 中添加

dependencies {

? ? implementation 'com.zackratos.ultimatebarx:ultimatebarx:0.1.1'

}

如果需要設(shè)置狀態(tài)了,可以在 Activity 中

UltimateBarX.create(UltimateBarX.STATUS_BAR)? ? ? ? // 設(shè)置狀態(tài)欄

? ? .fitWindow(true)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // 布局是否侵入狀態(tài)欄(true 不侵入,false 侵入)

? ? .bgColor(Color.BLACK)? ? ? ? ? ? ? ? ? ? ? ? ? // 狀態(tài)欄背景顏色(色值)

? ? .bgColorRes(R.color.deepSkyBlue)? ? ? ? ? ? ? ? // 狀態(tài)欄背景顏色(資源id)

? ? .bgRes(R.drawable.bg_gradient)? ? ? ? ? ? ? ? ? // 狀態(tài)欄背景 drawable

? ? .light(false)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // light模式(狀態(tài)欄字體灰色 Android 6.0 以上支持)

? ? .apply(this);

方法非常簡單,注釋也寫的很清楚了,這里有三個設(shè)置背景的方法,寫一個就行了,多寫也只有一個會生效,優(yōu)先級 bgRes > bgColor > bgColorRes,如果需要設(shè)置導(dǎo)航欄,在 create 里面?zhèn)魅?UltimateBarX.NAVIGATION_BAR即可,其他不變,狀態(tài)欄和導(dǎo)航欄完全獨立設(shè)置,互不影響,做到了真正的解耦。

如果要實現(xiàn)上面所說的需求,只需要在剛進入 Activity 的時候

UltimateBarX.create(UltimateBarX.NAVIGATION_BAR)

? ? .fitWindow(false)

? ? .bgColor(Color.parseColor("#66000000"))

? ? .apply(this);

設(shè)置布局侵入到導(dǎo)航欄,然后點擊按鈕,讓布局侵入到狀態(tài)欄,只需

UltimateBarX.create(UltimateBarX.STATUS_BAR)

? ? .fitWindow(false)

? ? .bgColor(Color.parseColor("#66000000"))

? ? .apply(this);

關(guān)于 light 方法

這里面有個 light 方法,用來設(shè)置狀態(tài)欄或者導(dǎo)航欄的 light 模式,當(dāng) light 模式為 true 時,狀態(tài)欄的字體會變灰,對應(yīng)的導(dǎo)航欄的按鈕會變灰,這里有個問題,就是設(shè)置 light 模式也需要調(diào)用 decorView 的setSystemUiVisibility 方法,這就意味著 light 模式是需要保存狀態(tài)的,如果不保存,第一次設(shè)置狀態(tài)欄為 light 模式,第二次再設(shè)置導(dǎo)航欄的時候,狀態(tài)欄的 light 模式就會被清除,這個前面已經(jīng)解釋過了。

那如何保存狀態(tài)呢,為了使侵入性更低,可以用一個單例對象來保存每個 Activity 的狀態(tài)欄和導(dǎo)航欄的 light 狀態(tài),這樣又會有一個問題,在 Activity 退出的時候,必須把當(dāng)前 Activity 對象從單例里面移除,否則會造成內(nèi)存泄漏,那么怎樣監(jiān)聽 Activity 退出呢,常用的做法就是在 Activity 里面添加一個看不見的 Fragment,當(dāng) Fragment 的 onDestroy 方法被調(diào)用的時候,說明 Activity 的 onDestroy 被調(diào)用了,即 Activity 已退出。

不過現(xiàn)在已經(jīng)不用這么麻煩了,Google 爸爸在 JetPack 組件里面,給我們提供了非常好用的工具「Lifecycle」,可以無侵入的監(jiān)聽 Activity 和 Fragment 的生命周期,這里就使用 Lifecycle 來實現(xiàn)的,非常方便,其實 Lifecycle 的實現(xiàn)原理也是在內(nèi)部添加一個 Fragment 來監(jiān)聽的。

最后

一點題外話:

我們有《Android學(xué)習(xí)、面試;文檔、視頻資源免費獲取》,可復(fù)制鏈接后用石墨文檔 App 或小程序打開鏈接或者私信我資料領(lǐng)取。

https://shimo.im/docs/TG8PDh9D96WGTT8W

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容