小编在做沉浸式状态栏功能时,遇到一个这样的问题:
当我在一个Dialog的onCreate()方法中执行下面的代码:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.dialog_fullscreen);
Window window = getWindow();
if (window != null) {
window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams
.MATCH_PARENT);
window.setGravity(Gravity.TOP);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.setStatusBarColor(Color.GREEN);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
}
}
}
效果是这样的:
当我把第7行代码改成这样:
window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
效果变成了这样:
大家仔细观察会发现当弹窗设置全屏时状态栏的图标文字是黑色的,而设置成非全屏时图标文字却是白色的。而这两种状态下我都通过代码window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR)设置状态栏为亮色模式(状态栏图标和文字变成黑色),但是非全屏时却失效了,这是为什么呢?下面通过源码分析一下:
在LightBarController.java中,方法updateStatus()是用来设置状态栏图标颜色的:
private void updateStatus(Rect fullscreenStackBounds, Rect dockedStackBounds) {
boolean hasDockedStack = !dockedStackBounds.isEmpty();
// If both are light or fullscreen is light and there is no docked stack, all icons get
// dark.
if ((mFullscreenLight && mDockedLight) || (mFullscreenLight && !hasDockedStack)) {
mStatusBarIconController.setIconsDarkArea(null);
mStatusBarIconController.getTransitionsController().setIconsDark(true, animateChange());
}
// If no one is light or the fullscreen is not light and there is no docked stack,
// all icons become white.
else if ((!mFullscreenLight && !mDockedLight) || (!mFullscreenLight && !hasDockedStack)) {
mStatusBarIconController.getTransitionsController().setIconsDark(
false, animateChange());
}
// Not the same for every stack, magic!
else {
Rect bounds = mFullscreenLight ? fullscreenStackBounds : dockedStackBounds;
if (bounds.isEmpty()) {
mStatusBarIconController.setIconsDarkArea(null);
} else {
mStatusBarIconController.setIconsDarkArea(bounds);
}
mStatusBarIconController.getTransitionsController().setIconsDark(true, animateChange());
}
}
这里解释如下:
- 如果在全屏和Docked下开启亮色模式或者在全屏下开启亮色模式且没有Docked栈时,状态栏图标设成黑色;
- 如果在全屏和Docked下均未开启亮色模式或者在全屏下未开启亮色模式且没有Docked栈时,状态栏图标设成白色;
- 如果以上情况均不符,则状态栏图标设成黑色。
那么什么叫Docked呢?其实Android为了支持多窗口,在运行时创建了多个Stack,系统中可能会包含这么几个Stack:
- Home Stack:这个是Launcher所在的Stack。 其实还有一些系统界面也运行在这个Stack上,例如近期任务;
- FullScreen Stack:全屏的Activity所在的Stack;
- Freeform模式的Activity所在Stack;
- Docked Stack:在分屏模式下,屏幕有一半运行了一个固定的应用(一般在上方),这个就是这里的Docked Stack;
- Pinned Stack:这个是画中画Activity所在的Stack。
了解了Fullscreen和Docked栈的概念,再加上上面的代码,我们就可以得出结论,其实只有全屏或分屏时应用处于屏幕的上方,才能修改状态栏的颜色。这个大家也可以自己测试一下。
那么,mFullscreenLight和mDockedLight是怎么来的呢?这个可以追溯到调用的代码:
public void onSystemUiVisibilityChanged(int fullscreenStackVis, int dockedStackVis,
int mask, Rect fullscreenStackBounds, Rect dockedStackBounds, boolean sbModeChanged,
int statusBarMode) {
int oldFullscreen = mFullscreenStackVisibility;
int newFullscreen = (oldFullscreen & ~mask) | (fullscreenStackVis & mask);
int diffFullscreen = newFullscreen ^ oldFullscreen;
int oldDocked = mDockedStackVisibility;
int newDocked = (oldDocked & ~mask) | (dockedStackVis & mask);
int diffDocked = newDocked ^ oldDocked;
if ((diffFullscreen & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0
|| (diffDocked & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0
|| sbModeChanged
|| !mLastFullscreenBounds.equals(fullscreenStackBounds)
|| !mLastDockedBounds.equals(dockedStackBounds)) {
mFullscreenLight = isLight(newFullscreen, statusBarMode,
View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
mDockedLight = isLight(newDocked, statusBarMode, View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
updateStatus(fullscreenStackBounds, dockedStackBounds);
}
mFullscreenStackVisibility = newFullscreen;
mDockedStackVisibility = newDocked;
mLastStatusBarMode = statusBarMode;
mLastFullscreenBounds.set(fullscreenStackBounds);
mLastDockedBounds.set(dockedStackBounds);
}
在这里可以看到16-18行就是赋值的地方,同时也看到了这里使用了View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR常量。
再跟进isLight()方法:
private boolean isLight(int vis, int barMode, int flag) {
boolean isTransparentBar = (barMode == MODE_TRANSPARENT
|| barMode == MODE_LIGHTS_OUT_TRANSPARENT);
boolean light = (vis & flag) != 0;
return isTransparentBar && light;
}
这里的第4行可以看到,其实就是newFullscreen或者newDocked和SYSTEM_UI_FLAG_LIGHT_STATUS_BAR按位与来判断是否为亮色模式。至于newFullscreen和newDocked,它们是传递过来的fullscreenStackVis和dockedStackVis再进行一些位操作生成的,而fullscreenStackVis和dockedStackVis是一层一层的方法调用传过来的,这里我们就不具体分析了,有兴趣的读者可以自己再去研究。
以上的分析适用于Activity和Dialog等所持有的Window,那么如果读者有做类似于我示例中的半弹窗效果,并且还有设置状态栏需求的话,我们可以改成做一个全屏的弹窗,然后把不想展示出来的界面设成透明的背景,就可以实现我们的需求啦!
个人一点拙见,有什么不对的地方欢迎大家一起交流~
参考:
https://paul.pub/android-multiwindow/
最后,欢迎加我微信 jimmysun8388
一起交流学习!
加好友时请注明申请理由,例如「姓名/昵称 + Android 交流」,示例:张三 Android 交流。