Android沉浸式狀態欄的實現方案探討
多次嘗試實現Android沉浸式狀態欄,資料很多也很雜。并且有好幾種實現方案,網上有好些資料把幾種方案都混在一起,暫時把效果實現了,但是遇到問題后就蛋疼了。于是,這兩天我就把從根源上把這幾種方案的原理都整理了一下。主要有四種方案,有的方案還可以細分:
- WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
- Window.setStatusBarColor(int);
- View.setSystemUiVisibility(visibility);
- Window.setAttributes(params);
首先創建一個demo,在LinearLayout布局中顯示一個全屏的ImageView:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/blue"
android:orientation="vertical">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY"
android:src="@drawable/bg"/>
</LinearLayout>
為了讓效果明顯,將decorview的背景設置為黃色:
getWindow().getDecorView().setBackgroundColor(Color.YELLOW);
把主題背景設置為紅色
<style name="AppTheme" parent="@style/BaseAppTheme">
<item name="android:background">@color/red</item>
</style>
運行效果如下圖:

這里寫圖片描述
方案一FLAG_TRANSLUCENT_STATUS全屏布局
在values-v19目錄下增加一個style:
<style name="AppTheme" parent="@style/BaseAppTheme">
<item name="android:windowTranslucentStatus">true</item>
<item name="android:windowTranslucentNavigation">true</item>
<item name="android:background">@color/red</item>
</style>
或者使用java代碼實現:
// 在UI線程任何時候都可以調用
private void setTranslucentSystemUI() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Window window = getWindow();
// Translucent status bar
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
// Translucent navigation bar
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
}
}

這里寫圖片描述
可以看到,圖片已經延伸至狀態欄和導航欄下面了,效果類似QQ空間頭部背景圖片。
有很多布局是不能這樣顯示的,因為狀態欄的信息會覆蓋住布局頂部的內容,于是我們在根布局設置一個屬性:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/blue"
android:fitsSystemWindows="true"
android:orientation="vertical">

這里寫圖片描述
為了效果明顯一點,我把背景設置為藍色。可以看到此時的效果與第一步的區別僅僅是狀態欄和導航欄的顏色為根布局的背景色,并且沒有看到decorview的黃色背景。通過Hierarchy View也可以看到,導航欄和狀態欄的view不見了,也就是全屏都是我們自己的Layout。所以說設置 android:fitsSystemWindows="true" 之后,系統自動給我們的根布局加上了padding,并且paddingTop為狀態欄高度,paddingBottom為導航欄高度。

這里寫圖片描述
在這里引申出一個問題:狀態欄和導航欄的view已經不見了,為何狀態欄的信息還能顯示?在此只是猜想一下是系統直接繪制在上面的,具體還得扒源碼。到這里,之前我們說改變狀態欄的顏色,這個說法是有問題的,因為我們改變的僅僅是狀態欄下面的背景而已,狀態欄本身只是一個懸浮在界面最上層的一行信息,而下面等高的一個view讓我們認為狀態欄的存在。等會兒順便說一下如何隱藏狀態欄信息,即真正的全屏。

這里寫圖片描述
方案二通過Window.setStatusBarColor(int)設置狀態欄顏色
Android5.1加入了新的方法來設置狀態欄和導航欄背景色
private void setTranslucentSystemUI() {
Window window = getWindow();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.setStatusBarColor(Color.TRANSPARENT);
window.setNavigationBarColor(Color.TRANSPARENT);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// Translucent status bar
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
// Translucent navigation bar
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
}
}

這里寫圖片描述
導航欄的顏色為decorview的黃色(三個操作按鈕還在,只是看不出清楚),狀態欄則顯示了紅色的主題背景,說明設置的主題背景是賦值到DecorView里面的mContentParent(DecorView里的子節點LinearLayout)的背景。
window.setStatusBarColor(Color.GREEN);
window.setNavigationBarColor(Color.GREEN);

這里寫圖片描述

這里寫圖片描述
上面兩張圖可以看出,狀態欄和導航欄的背景view是覆蓋在 DecorView.LinearLayout 上面的。順便提醒一下,下面這樣設置狀態欄是無效的:
window.setStatusBarColor(Color.GREEN);
window.setNavigationBarColor(Color.GREEN);
// Translucent status bar
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
// Translucent navigation bar
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
從上面的分析也能知道,設置 FLAG_TRANSLUCENT_STATUS 后,狀態欄背景view都不在了,設置顏色自然是沒有效果的。
方案三View.setSystemUiVisibility(visibility)
View.setSystemUiVisibility(visibility) 設置狀態欄與導航欄顯示與否
在SDK16加入了一些屬性( View.SYSTEM_UI_FLAG_XXX )來控制系統UI(狀態欄和導航欄)
private void setSystemUIVisible(boolean visible) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
return;
}
int visibility = getWindow().getDecorView().getSystemUiVisibility();
if (visible) {
visibility &= (~View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)
& (~View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)
// & (~View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)
& (~View.SYSTEM_UI_FLAG_FULLSCREEN)
// & (~View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
;
} else {
visibility |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_FULLSCREEN // 隱藏狀態欄
// 全屏布局,狀態欄(非透明背景)會蓋在布局上
// 與SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN一起使用后,可全屏顯示布局
// | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // 隱藏狀態欄
// 全屏布局,導航欄(非透明背景)會蓋在布局上
// | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
;
}
getWindow().getDecorView().setSystemUiVisibility(visibility);
}
在設置了 FLAG_TRANSLUCENT_STATUS 和 FLAG_TRANSLUCENT_NAVIGATION 之后,已經是全屏布局了,所以再增加 SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 和 SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 是沒有效果的,如果不用 FLAG_TRANSLUCENT_STATUS 的話,切換全屏布局和非全屏布局會導致界面重新布局而抖動,因為根容器的大小會發生改變。 SYSTEM_UI_FLAG_IMMERSIVE_STICKY 屬性可以讓界面在頂部下滑或底部上滑時顯示出SYSTEM_UI,并在3S后自動隱藏。
方案四Window.setAttributes(params)
還有一個方式動態切換全屏和非全屏(其實也是狀態欄的顯示與否):
private void setFullScreenEnable(boolean enable) {
Window window = getWindow();
WindowManager.LayoutParams params = window.getAttributes();
if (enable) {
// 布局占用狀態欄,并隱藏狀態欄,不影響導航欄
params.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN;
} else {
params.flags &= (~WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
// 全屏布局,狀態欄和導航欄覆蓋在布局上
window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
window.setAttributes(params);
}
設置 FLAG_LAYOUT_NO_LIMITS 屬性也可以將布局延伸到 狀態欄和導航欄 下面,也就是全屏布局。然后再切換 FLAG_FULLSCREEN 屬性時,只是隱藏與顯示狀態欄信息的效果。如果沒有設置 FLAG_LAYOUT_NO_LIMITS 屬性,切換 FLAG_FULLSCREEN 時,根容器的大小也會改變,所以會有界面抖動的情況。
來自:http://www.jianshu.com/p/a4d685422604