前段时间,突然收到一个状态栏颜色优化设计的任务,将原本应用整体的黑色状态栏修改为根据标题栏颜色进行沉浸式设计,显示效果如下:
image经过分析及踩过 N 多坑,终于完成了 APP 全局的修改。现将一些需要注意的问题及踩过的坑进行梳理总结,主要从系统版本区别、各大厂商的 ROM 区别及具体的设置进行分析,期间也参考了很多资料,会在文末附上对应的链接
首先我们需要注意,Android 不是各个版本都支持设置状态栏的颜色,只有在 5.0 以上才支持。另外 6.0 以上才支持设置状态栏黑色图标(避免白色状态栏及白色图标导致看不清电量 时间等问题)
image系统版本 | 是否支持设置状态栏颜色 | 是否允许设置状态栏黑色图标 |
---|---|---|
4.4 | 否 | 否 |
5.0 | 是 | 否 |
6.0+ | 是 | 是 |
这个问题一开始也困扰了我,后面分析,在原生的系统虽然设置了状态栏透明,但是状态栏区域也会有一层半透明的遮罩(估计就是考虑到白色状态栏引起的问题),但是测试发现部分国产 ROM 设置穿透栏透明则会完全透明(例如 MIUI)
原生系统效果如下:
MIUI 系统效果如下:
image原生 6.0 以上有 API 支持,但是国产各 ROM 经过定制,有的需要特定的设置才能实现
原生系统设置:
- public void setLightStatusBar(Window window, boolean lightStatusBar) {
- // 设置浅色状态栏时的界面显示
- View decor = window.getDecorView();
- int ui = decor.getSystemUiVisibility();
- if (lightStatusBar) {
- ui |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
- } else {
- ui &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
- }
- decor.setSystemUiVisibility(ui);
- }
小米:
- public static boolean MIUISetStatusBarLightMode(Window window, boolean dark) {
- boolean result = false;
- if (window != null) {
- Class clazz = window.getClass();
- try {
- int darkModeFlag = 0;
- Class layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams");
- Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE");
- darkModeFlag = field.getInt(layoutParams);
- Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class);
- if (dark) {
- extraFlagField.invoke(window, darkModeFlag, darkModeFlag); //状态栏透明且黑色字体
- } else {
- extraFlagField.invoke(window, 0, darkModeFlag); //清除黑色字体
- }
- result = true;
- } catch(Exception e) {
- }
- }
- return result;
- }
魅族:
- public static boolean FlymeSetStatusBarLightMode(Window window, boolean dark) {
- boolean result = false;
- if (window != null) {
- try {
- WindowManager.LayoutParams lp = window.getAttributes();
- Field darkFlag = WindowManager.LayoutParams.class.getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON");
- Field meizuFlags = WindowManager.LayoutParams.class.getDeclaredField("meizuFlags");
- darkFlag.setAccessible(true);
- meizuFlags.setAccessible(true);
- int bit = darkFlag.getInt(null);
- int value = meizuFlags.getInt(lp);
- if (dark) {
- value |= bit;
- } else {
- value &= ~bit;
- }
- meizuFlags.setInt(lp, value);
- window.setAttributes(lp);
- result = true;
- } catch(Exception e) {
- }
- }
- return result;
- }
华为手机: 部分测试发现华为的 EMUI 手机状态栏会跟系统桌面的状态栏一样,设置了没用,这里如果要特殊设置状态栏颜色,只能参考 4.4 的处理方式(后续介绍)
image通过上述的版本及分析,可见完善的的状态栏兼容是一个大工程,需要综合考虑系统版本及各个厂商 ROM 等因素。5.0 以上有系统 API 进行支持,这里我们主要来分析一些 4.4 的实现原理。 简单来说,4.4 的实现方式就是使用透明的状态栏,然后做一个和状态栏一样高度的 View,加入到 Windows 的 DecorView,然后给这个 View 设置背景色,达到实现状态栏颜色。
image
- window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
- ViewGroup decorViewGroup = (ViewGroup) window.getDecorView();
- View statusBarView = decorViewGroup.findViewWithTag(STATUS_BAR_VIEW_TAG);
- if (statusBarView == null) {
- statusBarView = new StatusBarView(window.getContext());
- statusBarView.setTag(STATUS_BAR_VIEW_TAG);
- FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT);
- params.gravity = Gravity.TOP;
- statusBarView.setLayoutParams(params);
- decorViewGroup.addView(statusBarView);
- }
- statusBarView.setBackgroundColor(color);
- StatusBarCompat.internalSetFitsSystemWindows(window, true);
注意 5.0 一般不用使用白色的状态栏(因为不能设置状态栏灰色图标),可在资源文件定义一个 rgb,区分版本,5.0 使用白灰色
image
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
- @Override
- public void setStatusBarColor(Window window, int color) {
- //取消设置透明状态栏,使 ContentView 内容不再覆盖状态栏
- window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
- //需要设置这个 flag 才能调用 setStatusBarColor 来设置状态栏颜色
- window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
- //设置状态栏颜色
- window.setStatusBarColor(color);
- }
image
- @TargetApi(Build.VERSION_CODES.M)
- @Override
- public void setStatusBarColor(Window window, int color) {
- //取消设置透明状态栏,使 ContentView 内容不再覆盖状态栏
- window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
- //需要设置这个 flag 才能调用 setStatusBarColor 来设置状态栏颜色
- window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
- //设置状态栏颜色
- window.setStatusBarColor(color);
- // 去掉系统状态栏下的windowContentOverlay
- View v = window.findViewById(android.R.id.content);
- if (v != null) {
- v.setForeground(null);
- }
- }
这里不重复造轮子,先提供一下 github 上比较完善的处理方案
StatusBarCompat 是一个用于设置系统状态栏颜色的兼容库,兼容 Android 4.4.2(API 19) 以上,使用简单,仅需要一行代码的调用。
支持 4.4 以上的,主要使用透明状态栏的方式实现
推荐使用 status-bar-compat,已考虑到整体的版本兼容机及各厂 ROM,调用简单。
- 在Activity的setContentView()方法调用之后,调用以下方法即可。
- StatusBarCompat.setStatusBarColor(this, color, lightStatusBar);
- 或者是
- StatusBarCompat.setStatusBarColor(this, color);
本文主要源码使用 status-bar-compat 中的代码进行说明
例如在应用中有全屏的看图页面,点击返回为非全屏(带状态栏)页面,非全屏页面由于现实状态,会出现页面抖动。目前暂无完善的处理方案,项目中暂时使用的方式是延迟全屏页面的 finish,先显示状态栏后再关闭
- 复写onBackPressed
- getActivity().getWindow().clearFlags(
- WindowManager.LayoutParams.FLAG_FULLSCREEN);
- if(getView()!=null){
- getView().postDelayed(new Runnable() {
- @Override
- public void run() {
- getActivity().finish();
- }
- },10);
- }
上面已有分析,要注意如果状态栏为白色,需要设置状态栏的图标颜色。status-bar-compat 中会把颜色转换成灰度值,然后自己控制状态栏图标颜色
- public static void setStatusBarColor(Activity activity, @ColorInt int color) {
- boolean isLightColor = toGrey(color) > 225;
- setStatusBarColor(activity, color, isLightColor);
- }
- /**
- * 把颜色转换成灰度值。
- * 代码来自 Flyme 示例代码
- */
- public static int toGrey(@ColorInt int color) {
- int blue = Color.blue(color);
- int green = Color.green(color);
- int red = Color.red(color);
- return (red * 38 + green * 75 + blue * 15) >> 7;
- }
这个目前也尚无方法,考虑可以在弹层出现时,动态修改状态的颜色,但是工作量比较大,可先适当调整弹层的 rgb,减低透明度
来源: https://juejin.im/entry/5a31c90bf265da431440b50e