Android关于沉浸式状态栏的一些总结
一、前言
其实我是不打算写这篇文章的,为什么呢?因为关于沉浸式状态栏的文章太多了,随便google一下就能出来几十上百篇文章,当然这其中有写的好的,也有滥竽充数的。前面在公众号推出了Material Design 的系列文章,就有读者留言,希望出一篇关于沉浸式的文章。因此这篇文章就整理总结一下各个版本的实现原理,顺便为大家推荐一个我觉得很方便的一个库。
二、沉浸式的一般套路
在介绍这个方便的轮子之前,我们先一起来回顾一下实现沉浸式状态栏的一般套路。在Android上,关于对StatusBar(状态栏)的操作,一直都在不断改善,并且表现越来越好,在Android4.4 以下,我们可以对StatusBar和 NavigationBar进行显示和隐藏操作。但是直到Android4.4,我们才能真正意义上的实现沉浸式状态栏。从Android4.4 到现在(Android 7.1),关于沉浸式大概可以分成三个阶段:
-
Android4.4(API 19) - Android 5.0(API 21): 这个阶段可以实现沉浸式,但是表现得还不是很好,实现方式为: 通过
FLAG_TRANSLUCENT_STATUS
设置状态栏为透明并且为全屏模式,然后通过添加一个与StatusBar 一样大小的View,将View 的 background 设置为我们想要的颜色,从而来实现沉浸式。 -
Android 5.0(API 21)以上版本: 在Android 5.0的时候,加入了一个重要的属性和方法
android:statusBarColor
(对应方法为 setStatusBarColor),通过这个方法我们就可以轻松实现沉浸式。也就是说,从Android5.0开始,系统才真正的支持沉浸式。 -
Android 6.0(API 23)以上版本:其实Android6.0以上的实现方式和Android 5.0 +是一样,为什么要将它归为一个单独重要的阶段呢?是因为从Android 6.0(API 23)开始,我们可以改状态栏的绘制模式,可以显示白色或浅黑色的内容和图标(除了魅族手机,魅族自家有做源码更改,6.0以下就能实现)
大概就是这个三个阶段,那么接下来我们就看一下这个三个阶段分别是如何来实现的。
2.1 Android4.4(API 19) - Android 5.0(API 21)实现沉浸式的方式
Android 4.4 为什么能够实现沉浸式的效果呢?因为在Android 4.4 新增了一个重要的属性:FLAG_TRANSLUCENT_STATUS
/**
* Window flag: request a translucent status bar with minimal system-provided
* background protection.
*
* <p>This flag can be controlled in your theme through the
* {@link android.R.attr#windowTranslucentStatus} attribute; this attribute
* is automatically set for you in the standard translucent decor themes
* such as
* {@link android.R.style#Theme_Holo_NoActionBar_TranslucentDecor},
* {@link android.R.style#Theme_Holo_Light_NoActionBar_TranslucentDecor},
* {@link android.R.style#Theme_DeviceDefault_NoActionBar_TranslucentDecor}, and
* {@link android.R.style#Theme_DeviceDefault_Light_NoActionBar_TranslucentDecor}.</p>
*
* <p>When this flag is enabled for a window, it automatically sets
* the system UI visibility flags {@link View#SYSTEM_UI_FLAG_LAYOUT_STABLE} and
* {@link View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}.</p>
*/
public static final int FLAG_TRANSLUCENT_STATUS = 0x04000000;
解释:设置状态栏透明,并且变为全屏模式。上面的解释已经说得很清楚了,当window的这个属性有效的时候,会自动设置 system ui visibility的标志
SYSTEM_UI_FLAG_LAYOUT_STABLE
和SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
。
有两种方式实现这个属性:
可以在代码中设置,如下:
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
当然也可以在theme 中设置属性windowTranslucentStatus
,如下:
android:windowTranslucentStatus
效果如下:
效果如上图,可以看出,沉浸式的效果是出来了,但是也有一个问题,我们的标题栏和状态栏重叠了,相当于整个布局上移了StatusBar 的高度。
为了让标题栏回到原来的位置,我们在标题栏的上方添加一个大小和StatusBar大小一样的View,View 的BackgroundColor 为标题栏一样的颜色,这个View起到一个占位的作用。这个时候,标题栏就会下移StatusBar的高度,回到正常的位置。
添加如下代码:
//获取windowphone下的decorView
ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
int count = decorView.getChildCount();
//判断是否已经添加了statusBarView
if (count > 0 && decorView.getChildAt(count - 1) instanceof StatusBarView) {
decorView.getChildAt(count - 1).setBackgroundColor(calculateStatusColor(color, statusBarAlpha));
} else {
//新建一个和状态栏高宽的view
StatusBarView statusView = createStatusBarView(activity, color, statusBarAlpha);
decorView.addView(statusView);
}
ViewGroup rootView = (ViewGroup) ((ViewGroup) activity.findViewById(android.R.id.content)).getChildAt(0);
//rootview不会为状态栏留出状态栏空间
ViewCompat.setFitsSystemWindows(rootView,true);
rootView.setClipToPadding(true);
创建和status bar 一样大小的View的代码如下:
private static StatusBarView createStatusBarView(Activity activity, int color, int alpha) {
// 绘制一个和状态栏一样高的矩形
StatusBarView statusBarView = new StatusBarView(activity);
LinearLayout.LayoutParams params =
new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(activity));
statusBarView.setLayoutParams(params);
statusBarView.setBackgroundColor(calculateStatusColor(color, alpha));
return statusBarView;
}
其中StatusBarView 就是一个普通的View。
添加上述代码后,效果如下:
通过以上就可以实现Android 4.4 上的沉浸式状态栏。
另外,如果是一张图片延伸到状态栏的话,直接设置FLAG_TRANSLUCENT_STATUS
就可以了,如下:
小结:Android4.4上实现沉浸式状态栏的套路是:为window添加
FLAG_TRANSLUCENT_STATUS
Flag,然后添加一个和status bar 一样大小的View 站位,从而让让标题栏不会与status bar 重叠。而图片延伸到状态栏只需要设置FLAG_TRANSLUCENT_STATUS
就OK。
前面说过,沉浸式在Android4.4 - Android5.0 之间的版本表现得不是很好,从上面贴的几张图就可以看出,状态栏的顶部有一个渐变,会显示出黑色的阴影(底部的导航栏也是一样的效果),在Android 5.0 版本已经被修复了。
2.2 Android 5.0(API 21)以上实现沉浸式的方式
Android 5.0 是一个里程碑式的版本,从Android 5.0开始,Google 推出了全新的设计规范 Material Design,并且原生控件就可以实现一些炫酷的UI动效。从这个版本开始,google 加入了一个比较重要的方法setStatusBarColor
(对应属性:android:statusBarColor
),通过这个方法,可以很轻松地实现沉浸式状态栏。方法如下:
/**
* Sets the color of the status bar to {@code color}.
*
* For this to take effect,
* the window must be drawing the system bar backgrounds with
* {@link android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS} and
* {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_STATUS} must not be set.
*
* If {@code color} is not opaque, consider setting
* {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_STABLE} and
* {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}.
* <p>
* The transitionName for the view background will be "android:status:background".
* </p>
*/
public abstract void setStatusBarColor(@ColorInt int color);
注意看这个方法的注释,想要这个方法生效,必须还要配合一个Flag一起使用,必须设置FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
,并且不能设置FLAG_TRANSLUCENT_STATUS
(Android 4.4才用这个)
我们来看一下FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
这个flag:
可以看到,这个flag 也是在Android 5.0添加的,它的作用是什么呢?
解释:设置了
FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
,表明会Window负责系统bar的background 绘制,绘制透明背景的系统bar(状态栏和导航栏),然后用getStatusBarColor()
和getNavigationBarColor()
的颜色填充相应的区域。这就是Android 5.0 以上实现沉浸式导航栏的原理。
实现沉浸式添加如下代码:
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
//注意要清除 FLAG_TRANSLUCENT_STATUS flag
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
getWindow().setStatusBarColor(getResources().getColor(android.R.color.holo_red_light));
效果如下:
当然也可以直接在Theme中使用,在values-v21文件夹下添加如下主题:
<style name="MDTheme" parent="Theme.Design.Light.NoActionBar">
<item name="android:windowTranslucentStatus">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:statusBarColor">@android:color/holo_red_light</item>
</style>
效果和上面代码中添加的效果一样,这里就不贴效果图了。
图片延伸到状态栏
在Android 5.0 使图片延伸到状态栏,只需设置windowTranslucentStatus
,将 statusBarColor 设置为透明即可:
<style name="ImageTranslucentTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="android:windowTranslucentNavigation">true</item>
<item name="android:windowTranslucentStatus">true</item>
<!-- 设置statusBarColor 为透明-->
<item name="android:statusBarColor">@android:color/transparent</item>
</style>
效果如下:
代码中通过版本号的判断兼容 Android5.0以下和Android 5.0以上:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
activity.getWindow().setStatusBarColor(calculateStatusColor(color, statusBarAlpha));
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
int count = decorView.getChildCount();
if (count > 0 && decorView.getChildAt(count - 1) instanceof StatusBarView) {
decorView.getChildAt(count - 1).setBackgroundColor(calculateStatusColor(color, statusBarAlpha));
} else {
StatusBarView statusView = createStatusBarView(activity, color, statusBarAlpha);
decorView.addView(statusView);
}
ViewGroup rootView = (ViewGroup) ((ViewGroup) activity.findViewById(android.R.id.content)).getChildAt(0);
rootView.setFitsSystemWindows(true);
rootView.setClipToPadding(true);
setRootView(activity);
}
2.3 Android 6.0 + 实现状态栏字色和图标浅黑色
使用沉浸式的时候会遇到一个问题,那就是Android 系统状态栏的字色和图标颜色为白色,当我的主题色或者图片接近白色或者为浅色的时候,状态栏上的内容就看不清了。 ,这个问题在Android 6.0的时候得到了解决。Android 6.0 新添加了一个属性SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
解释:为setSystemUiVisibility(int)方法添加的Flag,请求status bar 绘制模式,它可以兼容亮色背景的status bar 。要在设置了
FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
flag ,同时清除了FLAG_TRANSLUCENT_STATUS
flag 才会生效。
添加如下代码:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN|View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
效果如下:
除了在代码中添加以外,还可以直接在主题中使用属性:
<style name="MDTheme" parent="Theme.Design.Light.NoActionBar">
<item name="android:windowTranslucentStatus">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:statusBarColor">@android:color/holo_red_light</item>
<!-- Android 6.0以上 状态栏字色和图标为浅黑色-->
<item name="android:windowLightStatusBar">true</item>
</style>
注意:主题要放在values-v23文件夹下:
三、轮子StatusBarUtil
通过上面的介绍,其实将各个版本实现沉浸式的方式和原理都讲完了。但是或许当你真正去实践沉浸式状态栏的时候,你会感觉到无从下手,因此,我给大家推荐一个轮子StatusBarUtil,Github:https://github.com/laobie/StatusBarUtil。
为什么会推荐这个库呢?因为这个库就只有一个类StatusBarUtil
,使用起来很方便,就像一个工具类一样使用。里面封装了很多静态方法,直接使用就好。自己添加也很方便。介绍一下使用的一些场景:
需要在setContentView()
之后调用:
setContentView(R.layout.main_activity);
...
StatusBarUtil.setColor(MainActivity.this, mColor);
- 1,设置状态栏颜色
StatusBarUtil.setColor(Activity activity, int color)
- 2,设置状态栏半透明
StatusBarUtil.setTranslucent(Activity activity, int statusBarAlpha)
- 3,设置状态栏全透明
StatusBarUtil.setTransparent(Activity activity)
- 4,为包含 DrawerLayout 的界面设置状态栏颜色(也可以设置半透明和全透明)
StatusBarUtil.setColorForDrawerLayout(Activity activity, DrawerLayout drawerLayout, int color)
- 5,为使用 ImageView 作为头部的界面设置状态栏透明(常用的场景为详情页的Header部分)
StatusBarUtil.setTranslucentForImageView(Activity activity, int statusBarAlpha, View needOffsetView)
- 6,在 Fragment 中使用
四、最后
以上就是对于沉浸式状态栏的一些总结,希望可以给还没有使用沉浸式的同学一些帮助。如果你已经使用过沉浸式状态栏,也不仿看一下,可以对各个版本实现的原理有一个更深的了解。最后,推荐了一个不错的库,更确切的说,应该是一个不错的工具类。如有问题,欢迎交流。