关于动态修改系统状态栏背景、字体和图标颜色,以及动态显示或隐藏系统状态栏,一直都是都是许多项目的常规需求。但是,由于不同版本间的兼容性差异,网上的实现方法多种多样。并且,许多方法都会存在这样、那样的不足。使用麻烦不说,一不小心还会产生各种异常,令人得不偿失。
这里,我们希望使用一个统一、简洁的方法,实现一键修改系统状态栏背景和文字颜色。以及动态的控制系统状态栏的显示或隐藏。
知识储备:安卓在5.0之前,并没有提供可以修改系统状态栏背景色的方法,而在6.0之前,也没有提供修改状态栏文字和图标颜色的方法。另外,国内某些厂商,如小米、魅族等修改了官方API,可能导致修改方法无效等。
因此,要想修改系统状态栏背景色,我们需要系统在5.0以上,而需要修改状态栏文字和图标颜色,则需要系统在6.0以上。这里建议向上兼容,即只有系统在6.0以上才允许动态修改系统状态栏,否则如果5.0的设备将状态栏北京改为白色,而字体颜色无法修改,默认也是白色。那就无法看见了。好在随着安卓系统的不断更新,目前6.0以上系统的覆盖率已经达到90%以上,所以即使无法做到完全覆盖,我们也可以确保绝大部分的用户体验。
下面是具体方案:
1.设置状态栏的背景颜色
// 设置状态栏底色颜色 getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); getWindow().setStatusBarColor(parseColor);
方法非常简单,这个背景色可以设置为任意颜色值,并且可以在任何时刻修改。需要注意的是API必须是5.0以上才有用哦。
2.设置状态栏文字和图标颜色
文字和图标颜色并不能设置为任意颜色,他只有两种选择:亮色系(文字和图标颜色为黑灰色)和暗色系(文字和图标颜色为白色)
根据我们设置的状态栏背景色,我们需要动态修改文字和图标颜色,以保证它们能被看清。例如,当背景色为白色时,我们采用亮色系,当背景色为黑色时,我们采用暗色系。那么问题来了,我的程序怎么知道这个时候该采用哪种色系呢?如果靠人工判断那也太麻烦了。
别慌,官方为我们提供了一个判断方法:
ColorUtils.calculateLuminance(parseColor)
返回值的取值范围为0.0-1.0 当大于0.5时,我们就可以认为当前颜色为亮色系,需要将文字和图标颜色设置为黑灰色;反之,则将文字和图标颜色设置为白色。设置的方法也非常简单:
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);//亮色系,将文字和图标颜色设置为黑灰色
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);//暗色系,将文字和图标颜色设置为白色
这下就大功告成了吗?别急,上面我们说了国内某些厂商,如小米、魅族等修改了官方API,我们会发现这两个方法在小米和魅族的部分机型上会显示无效果。需要针对性的做出兼容处理。
//小米手机兼容处理,修改系统状态栏文字和图标颜色 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) { }
//魅族手机兼容处理,修改系统状态栏文字和图标颜色
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) { }
这是网上找到的修改方法,亲测好用。
那么,我们的完整动态修改系统状态栏背景和文字颜色方法如下:
/*设置状态栏颜色和文字颜色,小米,魅族需要自定义,比较坑爹*/ public static void setColor(Window window, int parseColor) { // 操作系统的api版本大于21,才能改变状态栏的颜色,api版本大于23,才能改变状态栏文字颜色 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { try { // 设置状态栏底色颜色 window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); window.setStatusBarColor(parseColor); // 如果亮色,设置状态栏文字为黑色 boolean dark = ColorUtils.calculateLuminance(parseColor) >= 0.5; if(MIUISetStatusBarLightMode(window,dark)){ //小米的设置成功 /*Logger.e("setColor","小米手机,设置成功");*/ }else if(FlymeSetStatusBarLightMode(window,dark)){ //魅族的设置成功 /*Logger.e("setColor","魅族手机,设置成功");*/ }else{ if (ColorUtils.calculateLuminance(parseColor) >= 0.5) { window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); } else { window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); } } } catch (Exception e) { e.printStackTrace(); } } } /** * 小米MIUI系统 亲测好用 * * @param window * @param dark * @return */ 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; } /** * 设置状态栏图标为深色和魅族特定的文字风格 * 可以用来判断是否为Flyme用户 * * @param window 需要设置的窗口 * @param dark 是否把状态栏字体及图标颜色设置为深色 * @return boolean 成功执行返回true */ 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; }
建议将以上方法放到你的工具类里,具体界面调用时只需一行代码就可以搞定了。
ToolsUtil.setColor(mContext.getWindow(), Color.WHITE);
至于动态控制系统状态栏的显示或隐藏。也很简单:
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_FULLSCREEN);//隐藏状态栏
动态显示状态栏直接用上面提到的的ToolsUtil.setColor方法,就可以一并实现了。
补充PS:至于网上说的那种沉浸式状态栏,由于需要调整布局,所以使用起来没这么简单,建议如非必要,不要这么做。具体方法给出如下:
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);//设置沉浸模式 getWindow().setStatusBarColor(Color.TRANSPARENT);//设置状态栏背景透明 //需要调整布局,为状态栏显示预留出空间
再次补充PS:还有一种随滑动手势实现状态栏颜色渐变的需求,本文没有提到。不过谷歌已经提供了很好的解决方案,CoordinatorLayout,有兴趣的可以自行百度一下。本文不做细讲,我们只说一下,什么时候去修改状态栏里的文字和图标颜色。
我们只需要监听AppBarLayout的onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset)方法,根据偏移量verticalOffset的值,动态设定状态栏里的文字和图标颜色即可。