国内很多类似的文章, 我只想说一个字, 真tm乱! 我看不懂… 评论里面 全在说无效什么的 (我试了也无效, 好厉害的样子)
不废话,回到正题, 首先贴上一个众所周知的库 SystemBarTint
我只要这个类
https://github.com/jgilfelt/SystemBarTint/blob/master/library/src/com/readystatesoftware/systembartint/SystemBarTintManager.java
然后复制到你的工程
这个类我就不多说了, 就是兼容4.x以上沉浸透明状态栏的 一个兼容类
1.开始
先贴工具类, 有部分代码参考自网上并有做改动,
public class StatusBarUtil {public final static int TYPE_MIUI = 0;public final static int TYPE_FLYME = 1;public final static int TYPE_M = 3;//6.0@IntDef({TYPE_MIUI,TYPE_FLYME,TYPE_M})@Retention(RetentionPolicy.SOURCE)@interface ViewType {}/*** 修改状态栏颜色,支持4.4以上版本** @param colorId 颜色*/public static void setStatusBarColor(Activity activity, int colorId) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {Window window = activity.getWindow();window.setStatusBarColor(colorId);} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {//使用SystemBarTintManager,需要先将状态栏设置为透明setTranslucentStatus(activity);SystemBarTintManager systemBarTintManager = new SystemBarTintManager(activity);systemBarTintManager.setStatusBarTintEnabled(true);//显示状态栏systemBarTintManager.setStatusBarTintColor(colorId);//设置状态栏颜色}}/*** 设置状态栏透明*/@TargetApi(19)public static void setTranslucentStatus(Activity activity) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {//5.x开始需要把颜色设置透明,否则导航栏会呈现系统默认的浅灰色Window window = activity.getWindow();View decorView = window.getDecorView();//两个 flag 要结合使用,表示让应用的主体内容占用系统状态栏的空间int option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN| View.SYSTEM_UI_FLAG_LAYOUT_STABLE;decorView.setSystemUiVisibility(option);window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);window.setStatusBarColor(Color.TRANSPARENT);//导航栏颜色也可以正常设置//window.setNavigationBarColor(Color.TRANSPARENT);} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {Window window = activity.getWindow();WindowManager.LayoutParams attributes = window.getAttributes();int flagTranslucentStatus = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;attributes.flags |= flagTranslucentStatus;//int flagTranslucentNavigation = WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;//attributes.flags |= flagTranslucentNavigation;window.setAttributes(attributes);}}/*** 代码实现android:fitsSystemWindows** @param activity*/public static void setRootViewFitsSystemWindows(Activity activity, boolean fitSystemWindows) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {ViewGroup winContent = (ViewGroup) activity.findViewById(android.R.id.content);if (winContent.getChildCount() > 0) {ViewGroup rootView = (ViewGroup) winContent.getChildAt(0);if (rootView != null) {rootView.setFitsSystemWindows(fitSystemWindows);}}}}/*** 设置状态栏深色浅色切换 */public static boolean setStatusBarDarkTheme(Activity activity, boolean dark) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {setStatusBarFontIconDark(activity, TYPE_M, dark);} else if (OSUtils.isMiui()) {setStatusBarFontIconDark(activity, TYPE_MIUI, dark);} else if (OSUtils.isFlyme()) {setStatusBarFontIconDark(activity, TYPE_FLYME, dark);} else {//其他情况return false;}return true;}return false;}/*** 设置 状态栏深色浅色切换*/public static boolean setStatusBarFontIconDark(Activity activity, @ViewType int type,boolean dark) {switch (type) {case TYPE_MIUI:return setMiuiUI(activity, dark);case TYPE_FLYME:return setFlymeUI(activity, dark);case TYPE_M:default:return setCommonUI(activity,dark);}}//设置6.0 状态栏深色浅色切换public static boolean setCommonUI(Activity activity, boolean dark) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {View decorView = activity.getWindow().getDecorView();if (decorView != null) {int vis = decorView.getSystemUiVisibility();if (dark) {vis |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;} else {vis &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;}if (decorView.getSystemUiVisibility() != vis) {decorView.setSystemUiVisibility(vis);}return true;}}return false;}//设置Flyme 状态栏深色浅色切换public static boolean setFlymeUI(Activity activity, boolean dark) {try {Window window = activity.getWindow();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);return true;} catch (Exception e) {e.printStackTrace();return false;}}//设置MIUI 状态栏深色浅色切换public static boolean setMiuiUI(Activity activity, boolean dark) {try {Window window = activity.getWindow();Class<?> clazz = activity.getWindow().getClass();@SuppressLint("PrivateApi") Class<?> layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams");Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE");int darkModeFlag = field.getInt(layoutParams);Method extraFlagField = clazz.getDeclaredMethod("setExtraFlags", int.class, int.class);extraFlagField.setAccessible(true);if (dark) { //状态栏亮色且黑色字体extraFlagField.invoke(window, darkModeFlag, darkModeFlag);} else {extraFlagField.invoke(window, 0, darkModeFlag);}return true;} catch (Exception e) {e.printStackTrace();return false;}}//获取状态栏高度public static int getStatusBarHeight(Context context) {int result = 0;int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");if (resourceId > 0) {result = context.getResources().getDimensionPixelSize(resourceId);}return result;}
}
好了,这个类 支持了 设置状态栏透明, 设置状态栏颜色, 支持了状态栏深色浅色切换(则状态栏上的文字图标颜色)
怕搞了一堆在别的文章的配置,所以我还是要说下以下代码不能出现:
全局搜索你的代码里 是否有 android:fitsSystemWindows , 删掉!
检查你的values、values-v19、values-v21等 是否配置了
如下item标签
// values-v19。v19 开始有 android:windowTranslucentStatus 这个属性
<style name="TranslucentTheme" parent="Theme.AppCompat.Light.NoActionBar"><item name="android:windowTranslucentStatus">true</item><item name="android:windowTranslucentNavigation">true</item>
</style>// values-v21。5.0 以上提供了 setStatusBarColor() 方法设置状态栏颜色。
<style name="TranslucentTheme" parent="Theme.AppCompat.Light.NoActionBar"><item name="android:windowTranslucentStatus">false</item><item name="android:windowTranslucentNavigation">true</item><!--Android 5.x开始需要把颜色设置透明,否则导航栏会呈现系统默认的浅灰色--><item name="android:statusBarColor">@android:color/transparent</item>
</style>
凡是在style.xml中 有关 windowTranslucentNavigation、windowTranslucentStatus、statusBarColor 统统删掉
因为 我们要通过代码去实现, xml中的各种属性全部不要写, 避免代码出现互相干扰, 出现各种 无效的bug
好了干扰已全部去除,开始适配
首先在Activity 的setContentView 下一行编写如下代码
@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.xxx);//设置状态栏透明StatusBarUtil.setTranslucentStatus(this);//一般的手机的状态栏文字和图标都是白色的, 可如果你的应用也是纯白色的, 或导致状态栏文字看不清//所以如果你是这种情况,请使用以下代码, 设置状态使用深色文字图标风格, 否则你可以选择性注释掉这个if内容if (!StatusBarUtil.setStatusBarDarkTheme(this, true)) {//如果不支持设置深色风格 为了兼容总不能让状态栏白白的看不清, 于是设置一个状态栏颜色为半透明,//这样半透明+白=灰, 状态栏的文字能看得清StatusBarUtil.setStatusBarColor(this,0x55000000);}
}
2.重新认识fitsSystemWindows
有没有遇到过一个问题: 写一个布局 想要背景图片沉浸进去, 但是我又想要图片沉浸进去但内容如何控制不要沉浸进去?
一开始我搞笑的弄了个自定义View 里面内置了paddingTop=状态栏高度 的功能, 效果挺好, 后来发现其实官方已经帮我们做好了 只是我们用不好fitSystemWindows而已… 现在跟我分析分析fitSystemWindows的原理
为了理解这个玩意的作用, 我看了下源码, 原来fitsSystemWindows=true可以理解为 paddingTop=状态栏高度, =false可以理解为沉浸进状态栏
当然你直接设置肯定没效果 你还要设置我上面说的StatusBarUtil.setTranslucentStatus(this);, 其实关键代码就是
window.statusBarColor = Color.TRANSPARENTwindow.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
把窗口全屏而已
现在来说说他们直接是如何传递的, fitsSystemWindows在ViewGroup中通过dispatchApplyWindowInsets进行分发给子View
如果dispatchApplyWindowInsets 中把insets.consumeSystemWindowInsets()消费掉, 那么inset事件就无法传递到子View,子View设置fitsSystemWindows=true 将会没有反应,View会通过onApplyWindowInsets消费掉WindowInsets, 当然其要求是父View必须设置fitSystemWindows=false 这样WindowInsets才能传递到子View中进行消费
那么, fitSystemWindows是如何实现paddingTop效果的呢?
源码发现
computeFitSystemWindows 方法把系统的状态栏高度, 导航栏高度等计算 最终放到localInsets 这个rect中, 此时的rect.top就是状态栏高度
然后通过applyInsets->internalSetPadding, 把mPaddingTop=localInsets.top 完成状态栏高度的padding
至此 简单的分析了fitSystemWindows的作用
那么我们具体如何控制呢, 其实很简单 我们先来看一个错误的示范
如图, 很多时候我们的既有认知认为是设置为子View就有效果…
其实…前面看源码说了 ,他是对子View再加paddingTop=状态栏高度
所以正确的做法是再包裹一层
完事. 这样你就可以随心所欲控制哪个View 要沉浸 哪个不沉浸
当然如果需要动态控制的话 可以重写父View dispatchApplyWindowInsets 进行分发控制
================================================================
================================================================
补充:
================================================================
================================================================
值得注意的是 如果你按我那样去做, 状态栏颜色无法被修改, 请检查上层布局是否设置了背景
或者受了全局主题的
<item name="android:windowBackground">@color/xxx</item>
的颜色影响
感谢@Narbolo 的提醒, 漏了个Rom类型判断的工具类,现在贴上
public class OSUtils {public static final String ROM_MIUI = "MIUI";public static final String ROM_EMUI = "EMUI";public static final String ROM_FLYME = "FLYME";public static final String ROM_OPPO = "OPPO";public static final String ROM_SMARTISAN = "SMARTISAN";public static final String ROM_VIVO = "VIVO";public static final String ROM_QIKU = "QIKU";private static final String KEY_VERSION_MIUI = "ro.miui.ui.version.name";private static final String KEY_VERSION_EMUI = "ro.build.version.emui";private static final String KEY_VERSION_OPPO = "ro.build.version.opporom";private static final String KEY_VERSION_SMARTISAN = "ro.smartisan.version";private static final String KEY_VERSION_VIVO = "ro.vivo.os.version";private static String sName;private static String sVersion;public static boolean isEmui() {return check(ROM_EMUI);}public static boolean isMiui() {return check(ROM_MIUI);}public static boolean isVivo() {return check(ROM_VIVO);}public static boolean isOppo() {return check(ROM_OPPO);}public static boolean isFlyme() {return check(ROM_FLYME);}public static boolean is360() {return check(ROM_QIKU) || check("360");}public static boolean isSmartisan() {return check(ROM_SMARTISAN);}public static String getName() {if (sName == null) {check("");}return sName;}public static String getVersion() {if (sVersion == null) {check("");}return sVersion;}public static boolean check(String rom) {if (sName != null) {return sName.equals(rom);}if (!TextUtils.isEmpty(sVersion = getProp(KEY_VERSION_MIUI))) {sName = ROM_MIUI;} else if (!TextUtils.isEmpty(sVersion = getProp(KEY_VERSION_EMUI))) {sName = ROM_EMUI;} else if (!TextUtils.isEmpty(sVersion = getProp(KEY_VERSION_OPPO))) {sName = ROM_OPPO;} else if (!TextUtils.isEmpty(sVersion = getProp(KEY_VERSION_VIVO))) {sName = ROM_VIVO;} else if (!TextUtils.isEmpty(sVersion = getProp(KEY_VERSION_SMARTISAN))) {sName = ROM_SMARTISAN;} else {sVersion = Build.DISPLAY;if (sVersion.toUpperCase().contains(ROM_FLYME)) {sName = ROM_FLYME;} else {sVersion = Build.UNKNOWN;sName = Build.MANUFACTURER.toUpperCase();}}return sName.equals(rom);}public static String getProp(String name) {String line = null;BufferedReader input = null;try {Process p = Runtime.getRuntime().exec("getprop " + name);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) {e.printStackTrace();}}}return line;}
}