DialogFragment显示不出StatusBar
2020-12-02 本文已影响0人
Magic旭
场景
在业务框架搭建下,App只有一个Activity,各种页面都是Fragment与Fragment交互,在直播场景交互复杂情况下,在直播页面的Fragment1可以点击调整各种页面Fragment2、Fragment3的场景下,假设采取manager.replace方法,会导致Fragment1在收到通知时候无法做出相应用户提示操作:弹窗、toast等,如果你硬要弹会崩溃的,亲。所以页面Fragment2、Fragment3都采用DialogFragment来进行用户交互。
DialogFragment
Window(简单介绍,不在本章介绍范围)
- Window级别会拆分多个等级,设置不同等级会有不同的重叠效果。Window统一由WindowManager进行管理,大家有兴趣可以点击👉地址Window学习地址
- Window级别Dialog、popupWindow、toast之所以能悬浮在Activity上面,那是因为它们都各自创建属于自己的Window,而Activity的创建也是会创建一个系统级别的Window,android系统window只是接口,唯一实现该接口是phoneWindow类,所以具体逻辑我们都应该去PhoneWindow里面查看。
- 视图显示大概流程:创建一个PhoneWindow,根据用户使用的Window的style里面的属性值来进行flag设置、features设置、window的width、height设置。后续根据上面的features值对应的来加载layoutResource(Layout Inspector中的DecorView点击展开后的整体布局)对应布局作为DecorView的内容,从DecorView的内容(layoutResource,其实就是我们日常开发的layout的xml)中findViewById找到拿来装载开发者视图的ViewGroup,有趣的点就是google开发者把所有相关的xml布局中装载开发者视图的ViewGroup的id都命名为com.android.internal.R.id.content(大家很熟悉的Window类的static值:ID_ANDROID_CONTENT)
//代码在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的问题
- 刚刚上面都提了,系统源码会根据开发者设置的window style的一些属性做flag设置、features设置、window的width、height设置。工作上因为别人复制一大坨window的style,里面定义了各种window的style属性值,其中就包含我们今天的主角android:windowIsFloating。windowsIsFloating是表示Window弹窗是否悬浮状态出现。
- 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);
}
//省略部分源码
……
}
- 分析下FLAG_LAYOUT_IN_SCREEN和FLAG_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没有反应。
- 为什么上面说状态栏是装饰物?带着这个疑问,我们继续深入学习。
- 首先我们要找到statusBar怎么创建出来的,我愣是在PhoneWindow找呀找呀找,都一直没找到相关的。还好换下思路,跟着staturBarColor去找,终于给我找到statusBar的代码了。
- 实际上我们在上述layoutResource含义图解的图片可以看到statusBar是以一个名为statusBarBackground的View形式出现的,而且是和我们的layoutResource同一个层次。那么StatusBar就应该是在DecorView中创建的拉,果不其然,我们在DecorView里面找到我们要的代码。
//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);
- 为什么说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;
}
- 经过第五步的源码分析,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。