4.4 以上要做所谓沉浸式,其实不是真正意义上的沉浸式,只是一种透明状态栏。
而由于 Android API 的不同,需要考虑 4.4、5.0、6.0 前后的不同。
适配 5.0 和 6.0 以上
应用风格如果是白色的,想把状态栏也设置成白色的,会导致状态栏上的图标文字看不见了,经查询发现 6.0 以上可以修改状态栏图标文字风格,可以改成黑的,但是 6.0 以下版本无解。体验了 QQ 浏览器,因为网页大多都是纯白的,在 6.0 的手机上状态栏背景纯白,图标文字改成黑的了,但在 5.1 的手机上图标文字没法改,它是把背景做成灰色的了。
6.0 以下无法改状态栏图标文字颜色,只能控制颜色不要太白。
window = this.activity.getWindow();
decorView = window.getDecorView();
// 设置状态栏颜色
window.setStatusBarColor(statusBarColorBefore23);
6.0 以上可以根据状态栏要变化的颜色来调整状态栏图标文字的风格。
// isLightStatusBarAfter23 控制是否更改状态栏图标文字颜色
int flag = isLightStatusBarAfter23 ? View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR : View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
decorView.setSystemUiVisibility(flag);
window.setStatusBarColor(statusBarColorAfter23); // 设置状态栏颜色
适配 4.4
4.4 版本需要透明状态栏,将内容往下移,然后再加一个和状态栏一样大小的 View 覆盖到状态栏上面。
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 上这么加状态栏下面会有黑边
// ((ViewGroup)decorView.findViewById(android.R.id.content)).addView(view);
((ViewGroup)decorView).addView(view);
自动获取布局背景色
如果没指定颜色,自动获取根 View 的背景,还找不到的话,再找第一个子 View,一开始递归找第一个 View 的,感觉没什么意义,调用者一般应该明确传颜色,不传可能就是根 View 上设了背景之类。这就要考虑设的是颜色还是图片。第一个子 View 是图片还是普通 View 设了背景。因为如果是图片,就不能设置状态栏颜色或者盖个 View 上去,而是让状态栏透明,内容往下,让图片透上去,当然如果是子 View 的图片,还不能 setFitsSystemWindows。
private boolean setStatusBarWithViewBg(View view, boolean isRootView) {
Drawable drawable = view.getBackground();
if (drawable != null) { // 设置了背景
if (drawable instanceof ColorDrawable) {
statusBarColorBefore23 = statusBarColorAfter23 = ((ColorDrawable) drawable).getColor();
// ... 根据颜色去设置
} else { // 背景是一张图
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
view.setFitsSystemWindows(isRootView); // 如果第一个子View的图片,要顶上去,不要下来,只有根 View 才下来
// 如果是子 View,因为图片要上去,图片里的内容得下来,所以加个 Padding
view.setPadding(view.getPaddingLeft(),
view.getPaddingTop() +
(isRootView ? 0 :
getStatusBarHeight(activity),
view.getPaddingRight(), view.getPaddingBottom());
}
return true;
} else {
return false;
}
}
4.4 版本和 setFitsSystemWindows 各种奇怪问题
setFitsSystemWindows 设置一次后再设置就没用了,有时明明是 true 内容又跑上去了,明明是 false 确跑下来了,反正多次调用这方法就各种问题。还遇到过 setFitsSystemWindows 导致内容布局变化,如果不对每个 Activity 配置一次 android:configChanges="screenSize|screenLayout"
,引起 onCreate 的多次调用。
所以尽量用 setPadding 来调整位置。
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); // 加个Tag,下次直接获取该 View 更改颜色
((ViewGroup)decorView).addView(view);
}
/*
if (paddingTop == -1) {
paddingTop = rootView.getPaddingTop();
}
*/
rootView.setPadding(view.getPaddingLeft(),
paddingTop + statusBarHeight,
view.getPaddingRight(), view.getPaddingBottom());
项目中遇到一个问题,基类设置了一个默认的状态栏样式,但某些 Activity 要自己单独的样式,又创建了一个对象,结果专门做沉浸的这个类被构造了两遍,导致 paddingTop 计算错误。搞了两遍,第二次 paddingTop 变成了两个状态栏高度加原来自己的 paddingTop,花了好长时间才排查出来。
所以解决方案就是基类构造的对象作为属性保存下来,然后子类就用父类的属性。
状态的重置
因为考虑同一个 Activity 多次改变状态栏颜色的情况,遇到的一个比较烦的问题是,许多状态需要重置,不然就会影响下一次,而且如果设置图片又改成颜色的,那么要考虑的更多,一会希望图片内容顶到状态栏下面,一会希望内容能在状态栏下面。
后来考虑将颜色和图片的逻辑分开,因为有图片时要重置的和只是改状态栏颜色的不一样,放一起如果只是改状态栏颜色会走大量无意义的逻辑,当然 4.4 版本也是要将内容往下,也要特殊考虑。
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;
}
还有最后每次设置完效果后要充值颜色值,以免影响下次使用
statusBarColorBefore23 = statusBarColorAfter23 = 0;
isLightStatusBarAfter23 = true;
支持第三方 SDK 页面
如果是第三方的 SDK,跳转的 Activity 是 SDK 里面的,可以用 ActivityLifecycleCallbacks,在 ActivityLifecycleCallbacks 里可以拿到 Activity 的实例,这里可以做沉浸。
public void onActivityStarted(Activity activity) {
...
if (activity.getClass().getName().startsWith("第三方SDK包名前缀")) {
new PseudoImmersiveModeManager(activity)
.setStatusBarColor(Color.GRAY, Color.WHITE)
.setIsLightStatusBarAfter23(true)
.makeStatusBarImmersive();
}
...
}
之所以不在 onActivityCreated 里调用,是因为虽然 Activity 实例是有了,但是页面还没加载完成,获取 rootView 时报空指针。
支持 DialogFragment
在 onCreateDialog 或 onViewCreated 的回调里,反正就是 Dialog 创建好了后调用
getDialog().getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
View view = getDialog().getWindow().getDecorView();
view.setPadding(view.getPaddingLeft, statusBarHeight, view.getPaddingRight, view.getPaddingBottom);
详细代码请见 Github 地址 ,下面分别是在 5.0 和 6.0 手机上的效果: