读完本篇能够了解的内容
1.状态栏颜色设置
2.状态栏文字颜色设置
3.滑动过程中,动态变化状态栏与文字颜色
应评论区小伙伴要求,从项目中抽离出一个demo供大家参考,这个demo布局中没有用fitsSystemWindows=true这个属性,而写本篇文章时用了这个属性,这个属性在国内某些品牌手机上会不生效,所以demo就重新整理了一下。整理不易,如觉有用,欢迎star。
透明状态栏demo。
需求背景
因为我们产品的标题栏是白色,所以状态栏也得改成白色,这时就需要把状态栏文字颜色改成深色,于是就只考虑6.0以上的状态栏样式修改了。
所以注意,本篇文章最终解决的问题是:
Android6.0以上状态栏颜色与状态栏图标文字颜色的适配更改,如果需要在4.4以上就进行适配的话,本文可以做为参考。
废话不多说,先放几张最终测试版本的公司产品的截图上来。
接下来我们一步一步来完成整个过程
状态栏颜色的修改
/**
* 修改状态栏颜色,支持4.4以上版本
* @param activity
* @param colorId
*/
public static void setStatusBarColor(Activity activity, int colorId) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = activity.getWindow();
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(activity.getResources().getColor(colorId));
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
//使用SystemBarTint库使4.4版本状态栏变色,需要先将状态栏设置为透明
transparencyBar(activity);
SystemBarTintManager tintManager = new SystemBarTintManager(activity);
tintManager.setStatusBarTintEnabled(true);
tintManager.setStatusBarTintResource(colorId);
}
}
通过代码,我们可以了解到在Android5.0以上,可以直接通过以下两句代码实现状态栏颜色更改。而4.4至5.0这区间的版本号需要用到SystemBarTintManager
这个辅助类。而本文主要讲述6.0以上的配置,所以只需要关系以下两句代码,其他就不展开了。
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(activity.getResources().getColor(colorId));
状态栏文字颜色修改
状态栏文字颜色的更改,稍微有点麻烦,分为谷歌原生方式、小米、魅族三种情况进行修改。不过我们也一步一步来实现,始终相信,复杂源自简单。
谷歌原生方式修改
谷歌原生方式改变状态栏文字颜色,非常简单。
private static void setAndroidNativeLightStatusBar(Activity activity, boolean dark) {
View decor = activity.getWindow().getDecorView();
if (dark) {
decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
} else {
decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
}
}
其实就是DecorView两个不同的标志位之间的切换:
SYSTEM_UI_FLAG_LIGHT_STATUS_BAR , SYSTEM_UI_FLAG_LAYOUT_STABLE
这里有个注意点:
一旦用谷歌原生设置状态栏文字颜色的方法进行设置的话,因为一直会携带SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
这个flag,那么默认界面会变成全屏模式,需要在根布局中设置FitsSystemWindows
属性为true,所以我在基类的 process
方法中加入如下的代码。
@Override
protected void process(Bundle savedInstanceState) {
// 华为,OPPO机型在StatusBarUtil.setLightStatusBar后布局被顶到状态栏上去了
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
View content = ((ViewGroup) findViewById(android.R.id.content)).getChildAt(0);
if (content != null && !isUseFullScreenMode()) {
content.setFitsSystemWindows(true);
}
}
}
或者在xml文件的根布局中去添加如下代码:
android:fitsSystemWindows="true"
小米系统下状态栏文字颜色的修改
public static boolean MIUISetStatusBarLightMode(Activity activity, boolean dark) {
boolean result = false;
Window window = activity.getWindow();
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;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && RomUtils.isMiUIV7OrAbove()) {
//开发版 7.7.13 及以后版本采用了系统API,旧方法无效但不会报错,所以两个方式都要加上
if (dark) {
activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
} else {
activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
}
}
} catch (Exception e) {
}
}
return result;
}
魅族系统状态栏文字颜色修改
private static boolean setFlymeLightStatusBar(Activity activity, boolean dark) {
boolean result = false;
if (activity != null) {
try {
WindowManager.LayoutParams lp = activity.getWindow().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);
activity.getWindow().setAttributes(lp);
result = true;
} catch (Exception e) {
}
}
return result;
}
设置状态栏透明,启用全屏模式
@TargetApi(19)
public static void transparencyBar(Activity activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = activity.getWindow();
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(Color.TRANSPARENT);
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Window window = activity.getWindow();
window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
}
目前为止,有两处可能会将屏幕设置为全屏模式,一处是用谷歌原生修改方式,一处就是这里调用如上方法transparencyBar
。但是只有这里是我们真的需要将屏幕设置为全屏模式。所以你应该注意到了我在上面那段代码中setFitsSystemWindows时增加了一个判断条件isUseFullScreenMode
。
if (content != null && !isUseFullScreenMode()) {
content.setFitsSystemWindows(true);
}
判断不同厂商系统
既然小米和魅族需要区别对待,那么就得判断不同厂商的系统。小米是在MIUI6以上就可以对文字颜色进行修改
小米系统判断
private static boolean isMiUIV6OrAbove() {
try {
final Properties properties = new Properties();
properties.load(new FileInputStream(new File(Environment.getRootDirectory(), "build.prop")));
String uiCode = properties.getProperty(KEY_MIUI_VERSION_CODE, null);
if (uiCode != null) {
int code = Integer.parseInt(uiCode);
return code >= 4;
} else {
return false;
}
} catch (final Exception e) {
return false;
}
}
魅族系统判断
private static boolean isFlymeV4OrAbove() {
String displayId = Build.DISPLAY;
if (!TextUtils.isEmpty(displayId) && displayId.contains("Flyme")) {
String[] displayIdArray = displayId.split(" ");
for (String temp : displayIdArray) {
//版本号4以上,形如4.x.
if (temp.matches("^[4-9]\\.(\\d+\\.)+\\S*")) {
return true;
}
}
}
return false;
}
到此基本上所有功能就可以通过以上方式进行串联起来了。
后话
贴出我在基类中几个比较重要的方法:
@Override
protected void process(Bundle savedInstanceState) {
// 华为,OPPO机型在StatusBarUtil.setLightStatusBar后布局被顶到状态栏上去了
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
View content = ((ViewGroup) findViewById(android.R.id.content)).getChildAt(0);
if (content != null && !isUseFullScreenMode()) {
content.setFitsSystemWindows(true);
}
}
}
// 在setContentView之前执行
@Override
public void setStatusBar() {
/*
为统一标题栏与状态栏的颜色,我们需要更改状态栏的颜色,而状态栏文字颜色是在android 6.0之后才可以进行更改
所以统一在6.0之后进行文字状态栏的更改
*/
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (isUseFullScreenMode()) {
StatusBarUtil.transparencyBar(this);
} else {
StatusBarUtil.setStatusBarColor(this, setStatusBarColor());
}
if (isUserLightMode()) {
StatusBarUtil.setLightStatusBar(this, true);
}
}
}
// 是否设置成透明状态栏,即就是全屏模式
protected boolean isUseFullScreenMode() {
return false;
}
protected int setStatusBarColor() {
return R.color.white_1;
}
// 是否改变状态栏文字颜色为黑色,默认为黑色
protected boolean isUserLightMode() {
return true;
}
以上两个复写方法都会在oncreate中执行。
接下来是工具类几个方法
@TargetApi(19)
public static void transparencyBar(Activity activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = activity.getWindow();
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(Color.TRANSPARENT);
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Window window = activity.getWindow();
window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
}
/**
* 修改状态栏颜色,支持4.4以上版本
*
* @param activity
* @param colorId
*/
public static void setStatusBarColor(Activity activity, int colorId) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = activity.getWindow();
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(activity.getResources().getColor(colorId));
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
//使用SystemBarTint库使4.4版本状态栏变色,需要先将状态栏设置为透明
transparencyBar(activity);
SystemBarTintManager tintManager = new SystemBarTintManager(activity);
tintManager.setStatusBarTintEnabled(true);
tintManager.setStatusBarTintResource(colorId);
}
}
/**
* 修改状态栏文字颜色,这里小米,魅族区别对待。
*/
public static void setLightStatusBar(final Activity activity, final boolean dark) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
switch (RomUtils.getLightStatusBarAvailableRomType()) {
case RomUtils.AvailableRomType.MIUI:
MIUISetStatusBarLightMode(activity, dark);
break;
case RomUtils.AvailableRomType.FLYME:
setFlymeLightStatusBar(activity, dark);
break;
case RomUtils.AvailableRomType.ANDROID_NATIVE:
setAndroidNativeLightStatusBar(activity, dark);
break;
}
}
}
public static boolean MIUISetStatusBarLightMode(Activity activity, boolean dark) {
boolean result = false;
Window window = activity.getWindow();
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;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && RomUtils.isMiUIV7OrAbove()) {
//开发版 7.7.13 及以后版本采用了系统API,旧方法无效但不会报错,所以两个方式都要加上
if (dark) {
activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
} else {
activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
}
}
} catch (Exception e) {
}
}
return result;
}
private static boolean setFlymeLightStatusBar(Activity activity, boolean dark) {
boolean result = false;
if (activity != null) {
try {
WindowManager.LayoutParams lp = activity.getWindow().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);
activity.getWindow().setAttributes(lp);
result = true;
} catch (Exception e) {
}
}
return result;
}
private static void setAndroidNativeLightStatusBar(Activity activity, boolean dark) {
View decor = activity.getWindow().getDecorView();
if (dark) {
decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
} else {
decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
}
}
重点踩过的坑
第一个 滑动过程状态栏文字颜色发生变化时卡顿
解决思路:这类型问题因为有现象,可以看看去掉哪部分代码后,如果不卡顿了,那么基本上就是那部分代码的问题;
一般产生的原因:主线程中各类耗时操作,io流,网络请求,数据库,大量计算,大量垃圾对象产生引起的gc回收。
解决过程
1.最开始定位到判断机型的代码引起的卡顿,就尝试用线程池加载这部分代码。卡顿稍微好一些了。(这里又埋了一个坑,待会儿讲)
2.继续追踪代码,发现判断机型的代码开启了一个io流的操作
private static boolean isMIUIV6OrAbove() {
String miuiVersionCodeStr = getSystemProperty("ro.miui.ui.version.code");
if (!TextUtils.isEmpty(miuiVersionCodeStr)) {
try {
int miuiVersionCode = Integer.parseInt(miuiVersionCodeStr);
if (miuiVersionCode >= 4) {
return true;
}
} catch (Exception e) {}
}
return false;
}
private static String getSystemProperty(String propName) {
String line;
BufferedReader input = null;
try {
Process p = Runtime.getRuntime().exec("getprop " + propName);
input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024);
line = input.readLine();
input.close();
} catch (IOException ex) {
return null;
} finally {
if (input != null) {
try {
input.close();
} catch (IOException e) {
}
}
}
return line;
}
将其用另外的方法替换
private static boolean isMiUIV6OrAbove() {
try {
final Properties properties = new Properties();
properties.load(new FileInputStream(new File(Environment.getRootDirectory(), "build.prop")));
String uiCode = properties.getProperty(KEY_MIUI_VERSION_CODE, null);
if (uiCode != null) {
int code = Integer.parseInt(uiCode);
return code >= 4;
} else {
return false;
}
} catch (final Exception e) {
return false;
}
}
到此卡顿问题得到完美解决。
第二个,小米mix2手机加载部分界面时,界面变成一篇空白
思路和之前一样,一步一步排查到问题代码。还记得之前为了解决卡顿,在setLightStatusBar
方法中加入了一个线程池进行操作,把线程池去掉后,异常消失。之所以定位到这里,当时猜测的原因是高版本小米手机改变文字颜色为深色是用的谷歌原生方法修改,这里可能需要在setContentView之前调用,但是放入线程池当中就不能保证两者的执行先后顺序了。
总结
6.0改变文字颜色代码谷歌原生代码
View decor = activity.getWindow().getDecorView();
if (dark) {
decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
} else {
decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
}
小米和魅族需要单独调用其各自的修改代码(见最开始上面代码),小米在miui7之后又用了谷歌原生调用方法。谷歌原生调用方法会导致全屏模式,所以需要设置FitsSystemWindows属性
5.0以上改变状态栏颜色代码
Window window = activity.getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(activity.getResources().getColor(colorId));
5.0后设置为全屏模式,就是透明状态栏
首页一般都是四个fragment的tab,这时可能一个需要全屏模式,那么将这个首页的activity设置为全屏模式后,四个tab都是全屏模式了,这时需要手动给不需要全屏模式的几个fragment添加一个状态栏高度的view。
Window window = activity.getWindow();
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(Color.TRANSPARENT);
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
RomUtils类
因为有很人在要romutils中的代码,之前没有想到这一块会给大伙带来问题。现贴出整个代码如下:
public class RomUtils {
class AvailableRomType {
public static final int MIUI = 1;
public static final int FLYME = 2;
public static final int ANDROID_NATIVE = 3;
public static final int NA = 4;
}
public static int getLightStatusBarAvailableRomType() {
//开发版 7.7.13 及以后版本采用了系统API,旧方法无效但不会报错
if (isMiUIV7OrAbove()) {
return AvailableRomType.ANDROID_NATIVE;
}
if (isMiUIV6OrAbove()) {
return AvailableRomType.MIUI;
}
if (isFlymeV4OrAbove()) {
return AvailableRomType.FLYME;
}
if (isAndroidMOrAbove()) {
return AvailableRomType.ANDROID_NATIVE;
}
return AvailableRomType.NA;
}
//Flyme V4的displayId格式为 [Flyme OS 4.x.x.xA]
//Flyme V5的displayId格式为 [Flyme 5.x.x.x beta]
private static boolean isFlymeV4OrAbove() {
String displayId = Build.DISPLAY;
if (!TextUtils.isEmpty(displayId) && displayId.contains("Flyme")) {
String[] displayIdArray = displayId.split(" ");
for (String temp : displayIdArray) {
//版本号4以上,形如4.x.
if (temp.matches("^[4-9]\\.(\\d+\\.)+\\S*")) {
return true;
}
}
}
return false;
}
//Android Api 23以上
private static boolean isAndroidMOrAbove() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
}
private static final String KEY_MIUI_VERSION_CODE = "ro.miui.ui.version.code";
private static boolean isMiUIV6OrAbove() {
try {
final Properties properties = new Properties();
properties.load(new FileInputStream(new File(Environment.getRootDirectory(), "build.prop")));
String uiCode = properties.getProperty(KEY_MIUI_VERSION_CODE, null);
if (uiCode != null) {
int code = Integer.parseInt(uiCode);
return code >= 4;
} else {
return false;
}
} catch (final Exception e) {
return false;
}
}
static boolean isMiUIV7OrAbove() {
try {
final Properties properties = new Properties();
properties.load(new FileInputStream(new File(Environment.getRootDirectory(), "build.prop")));
String uiCode = properties.getProperty(KEY_MIUI_VERSION_CODE, null);
if (uiCode != null) {
int code = Integer.parseInt(uiCode);
return code >= 5;
} else {
return false;
}
} catch (final Exception e) {
return false;
}
}
}