讀完本篇能夠了解的內容 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); } }
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; } } }
作者:一盤好書
鏈接:https://www.jianshu.com/p/7392237bc1de
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。