Android进阶:非全屏的Window无法设置SYSTEM_UI_FLAG_LIGHT_STATUS_BAR问题分析

小编在做沉浸式状态栏功能时,遇到一个这样的问题:

当我在一个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);
            }
        }
    }
}

效果是这样的:

Android进阶:非全屏的Window无法设置SYSTEM_UI_FLAG_LIGHT_STATUS_BAR问题分析_第1张图片

当我把第7行代码改成这样:

window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);

效果变成了这样:

Android进阶:非全屏的Window无法设置SYSTEM_UI_FLAG_LIGHT_STATUS_BAR问题分析_第2张图片

大家仔细观察会发现当弹窗设置全屏时状态栏的图标文字是黑色的,而设置成非全屏时图标文字却是白色的。而这两种状态下我都通过代码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或者newDockedSYSTEM_UI_FLAG_LIGHT_STATUS_BAR按位与来判断是否为亮色模式。至于newFullscreennewDocked,它们是传递过来的fullscreenStackVisdockedStackVis再进行一些位操作生成的,而fullscreenStackVisdockedStackVis是一层一层的方法调用传过来的,这里我们就不具体分析了,有兴趣的读者可以自己再去研究。

以上的分析适用于Activity和Dialog等所持有的Window,那么如果读者有做类似于我示例中的半弹窗效果,并且还有设置状态栏需求的话,我们可以改成做一个全屏的弹窗,然后把不想展示出来的界面设成透明的背景,就可以实现我们的需求啦!

个人一点拙见,有什么不对的地方欢迎大家一起交流~

参考
https://paul.pub/android-multiwindow/

最后,欢迎加我微信 jimmysun8388 一起交流学习!

Android进阶:非全屏的Window无法设置SYSTEM_UI_FLAG_LIGHT_STATUS_BAR问题分析_第3张图片
微信二维码

加好友时请注明申请理由,例如「姓名/昵称 + Android 交流」,示例:张三 Android 交流。

你可能感兴趣的:(Android进阶:非全屏的Window无法设置SYSTEM_UI_FLAG_LIGHT_STATUS_BAR问题分析)