DialogFragment显示不出StatusBar

2020-12-02  本文已影响0人  Magic旭

场景

在业务框架搭建下,App只有一个Activity,各种页面都是Fragment与Fragment交互,在直播场景交互复杂情况下,在直播页面的Fragment1可以点击调整各种页面Fragment2、Fragment3的场景下,假设采取manager.replace方法,会导致Fragment1在收到通知时候无法做出相应用户提示操作:弹窗、toast等,如果你硬要弹会崩溃的,亲。所以页面Fragment2、Fragment3都采用DialogFragment来进行用户交互。

DialogFragment

Window(简单介绍,不在本章介绍范围)
//代码在PhoneWindow的generateLayout函数里
int layoutResource;
        int features = getLocalFeatures();
        // System.out.println("Features: 0x" + Integer.toHexString(features));
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss;
            setCloseOnSwipeEnabled(true);
        } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleIconsDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_title_icons;
            }
            // XXX Remove this once action bar supports these features.
            removeFeature(FEATURE_ACTION_BAR);
            // System.out.println("Title Icons!");
        } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
                && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
  ……


//代码在在PhoneWindow的generateLayout函数里,函数最后会把这个container返回,拿到container你想加什么都行拉。
//详细的大家可以自行搜索,有很多人写这部分写的很好,我就不在这里讲了。
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
layoutResource含义图解
Window的问题
  1. 刚刚上面都提了,系统源码会根据开发者设置的window style的一些属性做flag设置、features设置、window的width、height设置。工作上因为别人复制一大坨window的style,里面定义了各种window的style属性值,其中就包含我们今天的主角android:windowIsFloating。windowsIsFloating是表示Window弹窗是否悬浮状态出现。

  1. windowIsFloating的属性值在PhoneWindow中是有着重要意义的,稍有不慎用错,就会出现文章标题的问题。我们看下window这个属性值有什么作用。
//PhoneWindow的generateLayout函数
protected ViewGroup generateLayout(DecorView decor) {
         //省略部分源码
        ……
        mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
        int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
                & (~getForcedWindowFlags());
        if (mIsFloating) {
            //如果开发者设置windowIsFloating为true,window内容的大小就为内容自身大小
            setLayout(WRAP_CONTENT, WRAP_CONTENT);
            //设置Window的Flag值,大家开发这么久都知道,Flag是可以影响Window里内容如何展示的:沉浸式、透明状态栏等
            setFlags(0, flagsToUpdate);
        } else {
            setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
        }
        //省略部分源码
        ……
}

  1. 分析下FLAG_LAYOUT_IN_SCREENFLAG_LAYOUT_INSET_DECOR这两个常量作用,直接看接口注解就可以一目了然了。
    FLAG_LAYOUT_IN_SCREEN:窗口标志:将窗口置于整个屏幕中,忽略边框周围的装饰(例如状态栏)。窗口必须正确放置其内容才能考虑屏幕装饰。如{@link Window#setFlags}中所述,通常由Window为您设置此标志。
    FLAG_LAYOUT_INSET_DECOR:窗口标志:仅与{@link #FLAG_LAYOUT_IN_SCREEN}结合使用的特殊选项。在请求布局时屏幕上的窗口可能会出现在屏幕装饰的上方或下方例如状态栏。通过还包括此标志,窗口经理将报告所需的插入矩形,以确保屏幕装饰不覆盖您的内容。该标志通常是*按照{@link Window#setFlags}中的说明,由Window为您设置。
    理解:在保证开发者视图完整性的情况下,才会去考虑布局问题。当两个flag结合一起使用的话,就能看到我最终想要的效果图:statusBar在顶部占一定高度,我自己的视图在StatusBar下面进行展示,两者相互不重合,不影响对方。如果mIsFloating为true的话,那系统就只能让开发者的视图大小即为wrap_content(开发者视图的xml的根布局为match_parent都没有,因为装载开发者视图的容器是wrap_content),所以就算我按照staturBarColor的说明来使用,设置各种flag值,依旧statusBar没有反应。

  1. 为什么上面说状态栏是装饰物?带着这个疑问,我们继续深入学习。
//DecorView的属性值,ColorViewState只是封装一层数据结构,有兴趣可以自己去看下源码,不难的,无非就是:View引用、设置位置参数、id、展示形式等。
    private final ColorViewState mStatusColorViewState =
            new ColorViewState(STATUS_BAR_COLOR_VIEW_ATTRIBUTES);
    private final ColorViewState mNavigationColorViewState =
            new ColorViewState(NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES);

  1. 为什么说statusBar和Navigation是装饰者呢?因为对于PhoneWindow来说完全不关心这两个View长什么样子,是否添加都不关心,所以对于PhoneWindow来讲statusBar和Navigation即是装饰者。再来看看DecorView对两个装饰者如何操作?
//DecorView的updateColorViewInt函数中
private void updateColorViewInt(final ColorViewState state, int sysUiVis, int color,
            int dividerColor, int size, boolean verticalBar, boolean seascape, int sideMargin,
            boolean animate, boolean force) {
            //省略部分代码
            ……
            //view是Navigation or StatusBar
            View view = state.view;
            int resolvedHeight = verticalBar ? LayoutParams.MATCH_PARENT : size;
            int resolvedWidth = verticalBar ? size : LayoutParams.MATCH_PARENT;
            int resolvedGravity = verticalBar
                ? (seascape ? state.attributes.seascapeGravity : state.attributes.horizontalGravity)
                : state.attributes.verticalGravity;
           //如果当前Window的statusBar和Navigation还没被创建,先创建再addView
           if (view == null) {
            if (showView) {
                state.view = view = new View(mContext);
                ……
                addView(view, lp);
                updateColorViewTranslations();
            }
        } else {
              //省略部分代码
              ……
              if (lp.height != resolvedHeight || lp.width != resolvedWidth
                    || lp.gravity != resolvedGravity || lp.rightMargin != rightMargin
                    || lp.leftMargin != leftMargin) {
                lp.height = resolvedHeight;
                lp.width = resolvedWidth;
                lp.gravity = resolvedGravity;
                lp.rightMargin = rightMargin;
                lp.leftMargin = leftMargin;
                view.setLayoutParams(lp);
            }
            if (showView) {
                setColor(view, color, dividerColor, verticalBar, seascape);
            }
        }
       //下面就是过渡动画了
       if (visibilityChanged) {
            view.animate().cancel();
             //省略部分代码
              …… 
            } else {
                view.setAlpha(1.0f);
                view.setVisibility(showView ? VISIBLE : INVISIBLE);
            }
        }
      //state所有属性值均赋值完成
      state.visible = show;
      state.color = color;
}

  1. 经过第五步的源码分析,statusBar和Navigation已经添加到我们的DecorView里面了。那剩下的问题就只有,添加了statusBar后我们的装载开发者视图的ViewGroup(不懂ViewGroup是什么的看Window标题下的视图显示大概流程)如何增加自己的top和bottom的间距,让开发者视图不与statusBar和Navigation重叠呢?
//DecorView的updateColorViews函数里
WindowInsets updateColorViews(WindowInsets insets, boolean animate) {
        //省略部分代码
        …… 
        //根据window or view的flag值,算出Navigation状态值,是否消费部分View空间
        boolean hideNavigation = (sysUiVisibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0;
        boolean forceConsumingNavBar = (mForceWindowDrawsBarBackgrounds
                        && (attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0
                        && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0
                        && !hideNavigation)
                || (mLastShouldAlwaysConsumeSystemBars && hideNavigation);
      
        boolean consumingNavBar =
                ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
                        && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0
                        && !hideNavigation)
                || forceConsumingNavBar;
        //根据window or view的flag值,算出statusBar状态值,是否消费部分View空间
        boolean fullscreen = (sysUiVisibility & SYSTEM_UI_FLAG_FULLSCREEN) != 0
                || (attrs.flags & FLAG_FULLSCREEN) != 0;
        boolean consumingStatusBar = (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0
                && (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0
                && (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0
                && mForceWindowDrawsBarBackgrounds
                && mLastTopInset != 0
                || (mLastShouldAlwaysConsumeSystemBars && fullscreen);
        //根据上面的参数,计算出staturBar高度,Navigation的高度和左右边距
        int consumedTop = consumingStatusBar ? mLastTopInset : 0;
        int consumedRight = consumingNavBar ? mLastRightInset : 0;
        int consumedBottom = consumingNavBar ? mLastBottomInset : 0;
        int consumedLeft = consumingNavBar ? mLastLeftInset : 0;
        //下面就是纯粹的根据if else来改变装载开发者视图的ViewGroup的layoutParams了(这一步就回到大家熟悉的地方了)
        if (mContentRoot != null
                && mContentRoot.getLayoutParams() instanceof MarginLayoutParams) {
            MarginLayoutParams lp = (MarginLayoutParams) mContentRoot.getLayoutParams();
            if (lp.topMargin != consumedTop || lp.rightMargin != consumedRight
                    || lp.bottomMargin != consumedBottom || lp.leftMargin != consumedLeft) {
                lp.topMargin = consumedTop;
                lp.rightMargin = consumedRight;
                lp.bottomMargin = consumedBottom;
                lp.leftMargin = consumedLeft;
                mContentRoot.setLayoutParams(lp);
                 //省略部分代码
                  …… 
            }
            if (insets != null) {
                //竟然都消耗PhoneWindow的部分区域,那就要告诉PhoneWindow呀,把消耗区域值设置到insets里面(WindowInsets可自行查资料)
                insets = insets.inset(consumedLeft, consumedTop, consumedRight, consumedBottom);
            }
            //省略部分代码
            …… 
            if (insets != null) {
                    //告诉PhoneWindow我把你该部分区域已经消耗了,不可分配给别人了。
                   insets = insets.consumeStableInsets();
            }
            return insets;
        }
}

总结

上面说那么多,还没提及怎么解决?那最后来个总结吧。既然知道windowIsFloating为true时候,是不添加statusBar和Navigation的,那么我换成false即可。仅仅一个简单style属性类型,可以让我们从PhoneWindow -> DecorView创建一路看过来,在遇到问题时候从源码下手还是能学习到很多不错知识点的。
tips:当你的DialogFragment为loading样式、弹窗这些就不需要statusBar和Navigation了,可以直接设置为true。

上一篇下一篇

猜你喜欢

热点阅读