4.4 以上要做所謂沉浸式,其實(shí)不是真正意義上的沉浸式,只是一種透明狀態(tài)欄。
而由于 Android API 的不同,需要考慮 4.4、5.0、6.0 前后的不同。
適配 5.0 和 6.0 以上
應(yīng)用風(fēng)格如果是白色的,想把狀態(tài)欄也設(shè)置成白色的,會導(dǎo)致狀態(tài)欄上的圖標(biāo)文字看不見了,經(jīng)查詢發(fā)現(xiàn) 6.0 以上可以修改狀態(tài)欄圖標(biāo)文字風(fēng)格,可以改成黑的,但是 6.0 以下版本無解。體驗(yàn)了 QQ 瀏覽器,因?yàn)榫W(wǎng)頁大多都是純白的,在 6.0 的手機(jī)上狀態(tài)欄背景純白,圖標(biāo)文字改成黑的了,但在 5.1 的手機(jī)上圖標(biāo)文字沒法改,它是把背景做成灰色的了。
6.0 以下無法改狀態(tài)欄圖標(biāo)文字顏色,只能控制顏色不要太白。
window = this.activity.getWindow();
decorView = window.getDecorView();
// 設(shè)置狀態(tài)欄顏色
window.setStatusBarColor(statusBarColorBefore23);
6.0 以上可以根據(jù)狀態(tài)欄要變化的顏色來調(diào)整狀態(tài)欄圖標(biāo)文字的風(fēng)格。
// isLightStatusBarAfter23 控制是否更改狀態(tài)欄圖標(biāo)文字顏色
int flag = isLightStatusBarAfter23 ? View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR : View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
decorView.setSystemUiVisibility(flag);
window.setStatusBarColor(statusBarColorAfter23); // 設(shè)置狀態(tài)欄顏色
適配 4.4
4.4 版本需要透明狀態(tài)欄,將內(nèi)容往下移,然后再加一個(gè)和狀態(tài)欄一樣大小的 View 覆蓋到狀態(tài)欄上面。
rootView = ((ViewGroup)decorView.findViewById(android.R.id.content)).getChildAt(0);
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
rootView.setFitsSystemWindows(true);
View view = new View(activity);
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(activity));
view.setBackgroundColor(statusBarColorBefore23);
view.setLayoutParams(params);
// 過去有遇到過在某版 MIUI 上這么加狀態(tài)欄下面會有黑邊
// ((ViewGroup)decorView.findViewById(android.R.id.content)).addView(view);
((ViewGroup)decorView).addView(view);
自動獲取布局背景色
如果沒指定顏色,自動獲取根 View 的背景,還找不到的話,再找第一個(gè)子 View,一開始遞歸找第一個(gè) View 的,感覺沒什么意義,調(diào)用者一般應(yīng)該明確傳顏色,不傳可能就是根 View 上設(shè)了背景之類。這就要考慮設(shè)的是顏色還是圖片。第一個(gè)子 View 是圖片還是普通 View 設(shè)了背景。因?yàn)槿绻菆D片,就不能設(shè)置狀態(tài)欄顏色或者蓋個(gè) View 上去,而是讓狀態(tài)欄透明,內(nèi)容往下,讓圖片透上去,當(dāng)然如果是子 View 的圖片,還不能 setFitsSystemWindows。
private boolean setStatusBarWithViewBg(View view, boolean isRootView) {
Drawable drawable = view.getBackground();
if (drawable != null) { // 設(shè)置了背景
if (drawable instanceof ColorDrawable) {
statusBarColorBefore23 = statusBarColorAfter23 = ((ColorDrawable) drawable).getColor();
// ... 根據(jù)顏色去設(shè)置
} else { // 背景是一張圖
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
view.setFitsSystemWindows(isRootView); // 如果第一個(gè)子View的圖片,要頂上去,不要下來,只有根 View 才下來
// 如果是子 View,因?yàn)閳D片要上去,圖片里的內(nèi)容得下來,所以加個(gè) Padding
view.setPadding(view.getPaddingLeft(),
view.getPaddingTop() +
(isRootView ? 0 :
getStatusBarHeight(activity),
view.getPaddingRight(), view.getPaddingBottom());
}
return true;
} else {
return false;
}
}
4.4 版本和 setFitsSystemWindows 各種奇怪問題
setFitsSystemWindows 設(shè)置一次后再設(shè)置就沒用了,有時(shí)明明是 true 內(nèi)容又跑上去了,明明是 false 確跑下來了,反正多次調(diào)用這方法就各種問題。還遇到過 setFitsSystemWindows 導(dǎo)致內(nèi)容布局變化,如果不對每個(gè) Activity 配置一次 android:configChanges="screenSize|screenLayout",引起 onCreate 的多次調(diào)用。
所以盡量用 setPadding 來調(diào)整位置。
if (paddingTop == -1) {
paddingTop = rootView.getPaddingTop();
}
view.setPadding(view.getPaddingLeft(),
paddingTop + getStatusBarHeight(mActivity),
view.getPaddingRight(),
view.getPaddingBottom());
因此 4.4 版本也要修改
private static final String TAG_KITKAT = "kitkat";
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
View view = decorView.findViewWithTag(TAG_KITKAT);
if (view != null) {
view.setBackgroundColor(statusBarColorBefore23);
} else if (rootView instanceof ViewGroup) {
view = new View(mActivity);
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight);
view.setBackgroundColor(statusBarColorBefore23);
view.setLayoutParams(params);
view.setTag(TAG_KITKAT); // 加個(gè)Tag,下次直接獲取該 View 更改顏色
((ViewGroup)decorView).addView(view);
}
/*
if (paddingTop == -1) {
paddingTop = rootView.getPaddingTop();
}
*/
rootView.setPadding(view.getPaddingLeft(),
paddingTop + statusBarHeight,
view.getPaddingRight(), view.getPaddingBottom());
項(xiàng)目中遇到一個(gè)問題,基類設(shè)置了一個(gè)默認(rèn)的狀態(tài)欄樣式,但某些 Activity 要自己單獨(dú)的樣式,又創(chuàng)建了一個(gè)對象,結(jié)果專門做沉浸的這個(gè)類被構(gòu)造了兩遍,導(dǎo)致 paddingTop 計(jì)算錯(cuò)誤。搞了兩遍,第二次 paddingTop 變成了兩個(gè)狀態(tài)欄高度加原來自己的 paddingTop,花了好長時(shí)間才排查出來。
所以解決方案就是基類構(gòu)造的對象作為屬性保存下來,然后子類就用父類的屬性。
狀態(tài)的重置
因?yàn)榭紤]同一個(gè) Activity 多次改變狀態(tài)欄顏色的情況,遇到的一個(gè)比較煩的問題是,許多狀態(tài)需要重置,不然就會影響下一次,而且如果設(shè)置圖片又改成顏色的,那么要考慮的更多,一會希望圖片內(nèi)容頂?shù)綘顟B(tài)欄下面,一會希望內(nèi)容能在狀態(tài)欄下面。
后來考慮將顏色和圖片的邏輯分開,因?yàn)橛袌D片時(shí)要重置的和只是改狀態(tài)欄顏色的不一樣,放一起如果只是改狀態(tài)欄顏色會走大量無意義的邏輯,當(dāng)然 4.4 版本也是要將內(nèi)容往下,也要特殊考慮。
private void reset(int newMode) {
if (lastMode == MODE_IMAGE) {
if (firstChildPaddingTop >= 0 && firstChildView != null) {
setPaddingTop(firstChildView, firstChildPaddingTop);
}
if (rootPaddingTop >= 0) {
setPaddingTop(rootView, rootPaddingTop);
}
if (newMode == MODE_COLOR) {
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
} else if ((lastMode == MODE_COLOR) && (newMode == MODE_IMAGE)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // 5.0 以上
window.setStatusBarColor(Color.TRANSPARENT);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // 4.4
View view = decorView.findViewWithTag(TAG_KITKAT);
if (view != null) {
((ViewGroup) decorView).removeView(view); // 把前面加的 View 移除
}
if (rootPaddingTop >=0) {
setPaddingTop(rootView, rootPaddingTop);
}
}
}
lastMode = newMode;
}
還有最后每次設(shè)置完效果后要充值顏色值,以免影響下次使用
statusBarColorBefore23 = statusBarColorAfter23 = 0;
isLightStatusBarAfter23 = true;
支持第三方 SDK 頁面
如果是第三方的 SDK,跳轉(zhuǎn)的 Activity 是 SDK 里面的,可以用 ActivityLifecycleCallbacks,在 ActivityLifecycleCallbacks 里可以拿到 Activity 的實(shí)例,這里可以做沉浸。
public void onActivityStarted(Activity activity) {
...
if (activity.getClass().getName().startsWith("第三方SDK包名前綴")) {
new PseudoImmersiveModeManager(activity)
.setStatusBarColor(Color.GRAY, Color.WHITE)
.setIsLightStatusBarAfter23(true)
.makeStatusBarImmersive();
}
...
}
之所以不在 onActivityCreated 里調(diào)用,是因?yàn)殡m然 Activity 實(shí)例是有了,但是頁面還沒加載完成,獲取 rootView 時(shí)報(bào)空指針。
支持 DialogFragment
在 onCreateDialog 或 onViewCreated 的回調(diào)里,反正就是 Dialog 創(chuàng)建好了后調(diào)用
getDialog().getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
View view = getDialog().getWindow().getDecorView();
view.setPadding(view.getPaddingLeft, statusBarHeight, view.getPaddingRight, view.getPaddingBottom);
詳細(xì)代碼請見 Github 地址 ,下面分別是在 5.0 和 6.0 手機(jī)上的效果:

