美文网首页Android进阶之路Android开发经验谈Android开发
Android进阶:非全屏的Window无法设置SYSTEM_U

Android进阶:非全屏的Window无法设置SYSTEM_U

作者: 孙强Jimmy | 来源:发表于2019-05-26 17:29 被阅读11次

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

    当我在一个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或者newDockedSYSTEM_UI_FLAG_LIGHT_STATUS_BAR按位与来判断是否为亮色模式。至于newFullscreennewDocked,它们是传递过来的fullscreenStackVisdockedStackVis再进行一些位操作生成的,而fullscreenStackVisdockedStackVis是一层一层的方法调用传过来的,这里我们就不具体分析了,有兴趣的读者可以自己再去研究。

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

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

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

    相关文章

      网友评论

        本文标题:Android进阶:非全屏的Window无法设置SYSTEM_U

        本文链接:https://www.haomeiwen.com/subject/sndftctx.html