Android沉浸式状态栏的实现

需求

从API 19开始,也就是android 4.4 kitcat,android开始支持沉浸式状态栏。可以使状态栏看起来和我们的程序浑然一体,不再像之前那样突兀。几个月前,刚好做了个这方面的需求,记录一下踩了的坑。

一共两种类型的沉浸式,一种是将view直接伸到statusbar里去,另一种是直接给statusbar设置一个背景色。其中,有些需要将伸到statusbar里的页面,顶部还有一个类似titlebar的view,有交互,必须保证操作区域没有进入到状态栏中。设置背景色的需求则比较简单,直接换个纯色的背景色。

实现

一顿的搜索前辈们的各种技术文章之后发现,一切效果都是:不理想。可以参考知乎上的一个问答Android 5.0 如何实现将布局的内容延伸到状态栏,网上也找了一些开源类,大多修改状态栏颜色的和布局伸入状态栏的是分开的,没有在一起介绍的。而且发现有些方法中介绍的fitsSystemWindows属性的办法,对我来说简直就是噩梦,布局太复杂,试了好多遍都没成功。

修改状态栏颜色的方法

4.4的版本和5.x的版本,实现方式还有点区别。4.4是没有提供直接改状态栏的办法的,如果要修改状态栏颜色,就需要将状态栏直接隐藏,再在根布局上增加一个statusbar等高的view。

而在5.x的系统上,有一个方法叫,setStatusBarColor,可以直接修改状态栏的颜色。

4.4隐藏状态栏的代码如下,

1
2
Window window =  mActivity.getWindow();
window.addFlags(Window.FLAG_TRANSLUCENT_STATUS);

设置颜色比较简单,在4.4的系统,调用addFlags(Window.FLAG_TRANSLUCENT_STATUS),然后在根布局add一个状态栏高度的view;在5.x以上的系统,调用setStatusBarColor,就OK了。

将布局延伸到状态栏

将布局延伸到状态栏,需要做的就是隐藏掉状态栏,最初我也是按照搜索到的资料介绍,在5.x的系统上使用以下代码:

1
2
3
4
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
window.addFlags(Window.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(Color.TRANSPARENT);

结果,测试发现在华为EMUI 3.x的5.x系统上,沉浸式状态栏的效果失效了。于是在5.0的系统上,改成了在调用setStatusBarColor之前,调用addFlags(Window.FLAG_TRANSLUCENT_STATUS),没有使用上面的代码。

1
2
window.addFlags(Window.FLAG_TRANSLUCENT_STATUS);
window.setStatusBarColor(Color.TRANSPARENT);

实践发现addFlag(FLAG_TRANSLUCENT_STATUS)就能让状态栏隐藏,在4.4中,有一层由深变浅的渐变遮罩(这个貌似没办法),不会覆盖整个状态栏;在5.x的手机上,会有一个半透明的黑色遮罩,这个是覆盖的整个状态栏位置的。

为了解决5.0的黑色遮罩的问题,就需要调用window.setStatusBarColor(Color.TRANSPARENT),将状态栏完全设置成全透明的。

布局复杂

但是,在隐藏掉以后,实际上,时间、wifi、通知等这些都会存在,顶部的titlebar就需要向下移动statusbar的高度,否则,时间,信号,wifi这些会覆盖在titlebar上面。

解决的办法就是,需要将titlebar的位置,向下移动一个statusbar高度。由于这种类型的页面比较少,所以直接在有titlebar的布局中,include增加了一个0dp高度的view,而在需要将其显示时,初始化时修改view的高度。

为了修改颜色和布局伸入状态栏可以统一处理,statusbar就完全隐藏掉,再增加一个statusbar高度的自定义背景色的view。

代码

因为项目sdk版本的原因,我直接把Window类一些字段复制出来了。调用方式:

1
2
3
4
StatusBarManager statusBarManager = new StatusBarManager(activity);
//这里如果要移动titlebar,则在布局中指定为include的自定义statusbar的view
//如果不指定,则调用setStatusBarView();会自动加一个view
statusBarManager.setStatusBarView(view);

另外,有些布局,在伸入到状态栏以后,给用户展示的区域就会变小,这个没办法,需要手动调整,只要调用statusBarManager.getStatusBarHeight(),此方法(注意这里不是静态方法,静态方法有参数,是获取系统状态栏的高度的)会返回view的高度,如果没有就是0,所以调用时不用做是否api 19+的版本判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
public class StatusBarManager {

private static final int BUILD_VERSION_KITKAT = 19;
private static final int BUILD_VERSION_LOLLIPOP = 21;

//WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
public static final int FLAG_TRANSLUCENT_STATUS = 0x04000000;

//WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
public static final int FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS = 0x80000000;

private Activity mActivity;
private View statusBarView;
private int statusBarHeight;

public StatusBarManager(Activity activity) {
this.mActivity = activity;
statusBarHeight = getStatusBarHeight(activity);
}

public void setStatusBarView(View statusBarView) {
this.statusBarView = statusBarView;
setTransparent();
}
public void setStatusBarView() {
setTransparent();
}
public int getStatusBarHeight() {
return statusBarHeight;
}

/**
* 设置状态栏全透明
*
*/
private void setTransparent() {
if (Build.VERSION.SDK_INT < BUILD_VERSION_KITKAT) {
return;
}
if(statusBarHeight <= 0){
return;
}
transparentStatusBar();
showStatusBarView();
}
@TargetApi(19)
private void showStatusBarView() {
int color = mActivity.getResources().getColor(R.color.main_color);
if(statusBarView == null){
statusBarView = new View(mActivity);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
getStatusBarHeight(mActivity));
statusBarView.setLayoutParams(params);
statusBarView.setBackgroundColor(color);
ViewGroup decorView = (ViewGroup) mActivity.getWindow().getDecorView();
FrameLayout content = (FrameLayout) decorView.findViewById(android.R.id.content);
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) content.getChildAt(0).getLayoutParams();
layoutParams.setMargins(0,statusBarHeight,0,0);
decorView.addView(statusBarView);
}else{
ViewGroup.LayoutParams layoutParams = statusBarView.getLayoutParams();
layoutParams.height = getStatusBarHeight(mActivity);
statusBarView.setLayoutParams(layoutParams);
statusBarView.setBackgroundColor(color);
}

}

//参考上面注释掉的代码 因为需要用隐藏API 调用方式进行改成反射
private void transparentStatusBar(){
Window window = mActivity.getWindow();
if (Build.VERSION.SDK_INT >= BUILD_VERSION_LOLLIPOP) {
//不add此条flag 会导致在EMUI3.1(华为)上失效,add这个flag 会导致在其它机型上面添加一个半透明黑条
window.addFlags(FLAG_TRANSLUCENT_STATUS);
//下面的代码段是不加上面的flag时,要显示纯色的状态栏时需要加的代码 不用了
/* window.clearFlags(FLAG_TRANSLUCENT_STATUS);
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
window.addFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);*/
//因为需要用隐藏API,没有重新编译5.x版本的android.jar,使用的还是18的api,这里用的反射
try {
Class[] argsClass=new Class[]{int.class};
Method setStatusBarColorMethod = Window.class.getMethod("setStatusBarColor",argsClass);
setStatusBarColorMethod.invoke(window, Color.TRANSPARENT);
} catch (Exception e) {
e.printStackTrace();
}
}else{
window.addFlags(FLAG_TRANSLUCENT_STATUS);
}
}


/**
* 获取状态栏高度
*
* @param context context
* @return 状态栏高度
*/
private static int getStatusBarHeight(Context context) {
if (Build.VERSION.SDK_INT < BUILD_VERSION_KITKAT) {
return 0;
}
// 获得状态栏高度
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
return context.getResources().getDimensionPixelSize(resourceId);
}

public void setStatusbarVisibility(int visibility){
if(statusBarView != null) {
this.statusBarView.setVisibility(visibility);
}
}

public void setColor(int color){
if(statusBarView != null){
this.statusBarView.setBackgroundColor(color);
}
}

}