場(chǎng)景
在業(yè)務(wù)框架搭建下馆铁,App只有一個(gè)Activity,各種頁面都是Fragment與Fragment交互企蹭,在直播場(chǎng)景交互復(fù)雜情況下又兵,在直播頁面的Fragment1可以點(diǎn)擊調(diào)整各種頁面Fragment2、Fragment3的場(chǎng)景下枫笛,假設(shè)采取manager.replace方法吨灭,會(huì)導(dǎo)致Fragment1在收到通知時(shí)候無法做出相應(yīng)用戶提示操作:彈窗、toast等刑巧,如果你硬要彈會(huì)崩潰的喧兄,親。所以頁面Fragment2啊楚、Fragment3都采用DialogFragment來進(jìn)行用戶交互吠冤。
DialogFragment
Window(簡單介紹,不在本章介紹范圍)
- Window級(jí)別會(huì)拆分多個(gè)等級(jí)恭理,設(shè)置不同等級(jí)會(huì)有不同的重疊效果拯辙。Window統(tǒng)一由WindowManager進(jìn)行管理,大家有興趣可以點(diǎn)擊??地址Window學(xué)習(xí)地址
- Window級(jí)別Dialog颜价、popupWindow涯保、toast之所以能懸浮在Activity上面,那是因?yàn)樗鼈兌几髯詣?chuàng)建屬于自己的Window周伦,而Activity的創(chuàng)建也是會(huì)創(chuàng)建一個(gè)系統(tǒng)級(jí)別的Window夕春,android系統(tǒng)window只是接口,唯一實(shí)現(xiàn)該接口是phoneWindow類专挪,所以具體邏輯我們都應(yīng)該去PhoneWindow里面查看及志。
- 視圖顯示大概流程:創(chuàng)建一個(gè)PhoneWindow,根據(jù)用戶使用的Window的style里面的屬性值來進(jìn)行flag設(shè)置寨腔、features設(shè)置速侈、window的width、height設(shè)置迫卢。后續(xù)根據(jù)上面的features值對(duì)應(yīng)的來加載layoutResource(Layout Inspector中的DecorView點(diǎn)擊展開后的整體布局)對(duì)應(yīng)布局作為DecorView的內(nèi)容倚搬,從DecorView的內(nèi)容(layoutResource,其實(shí)就是我們?nèi)粘i_發(fā)的layout的xml)中findViewById找到拿來裝載開發(fā)者視圖的ViewGroup靖避,有趣的點(diǎn)就是google開發(fā)者把所有相關(guān)的xml布局中裝載開發(fā)者視圖的ViewGroup的id都命名為com.android.internal.R.id.content(大家很熟悉的Window類的static值:ID_ANDROID_CONTENT)
//代碼在PhoneWindow的generateLayout函數(shù)里
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函數(shù)里潭枣,函數(shù)最后會(huì)把這個(gè)container返回,拿到container你想加什么都行拉。
//詳細(xì)的大家可以自行搜索幻捏,有很多人寫這部分寫的很好盆犁,我就不在這里講了。
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
layoutResource含義圖解
Window的問題
- 剛剛上面都提了篡九,系統(tǒng)源碼會(huì)根據(jù)開發(fā)者設(shè)置的window style的一些屬性做flag設(shè)置谐岁、features設(shè)置、window的width榛臼、height設(shè)置伊佃。工作上因?yàn)閯e人復(fù)制一大坨window的style,里面定義了各種window的style屬性值沛善,其中就包含我們今天的主角android:windowIsFloating航揉。windowsIsFloating是表示W(wǎng)indow彈窗是否懸浮狀態(tài)出現(xiàn)。
- windowIsFloating的屬性值在PhoneWindow中是有著重要意義的金刁,稍有不慎用錯(cuò)帅涂,就會(huì)出現(xiàn)文章標(biāo)題的問題。我們看下window這個(gè)屬性值有什么作用尤蛮。
//PhoneWindow的generateLayout函數(shù)
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) {
//如果開發(fā)者設(shè)置windowIsFloating為true媳友,window內(nèi)容的大小就為內(nèi)容自身大小
setLayout(WRAP_CONTENT, WRAP_CONTENT);
//設(shè)置Window的Flag值,大家開發(fā)這么久都知道产捞,F(xiàn)lag是可以影響Window里內(nèi)容如何展示的:沉浸式醇锚、透明狀態(tài)欄等
setFlags(0, flagsToUpdate);
} else {
setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
}
//省略部分源碼
……
}
- 分析下FLAG_LAYOUT_IN_SCREEN和FLAG_LAYOUT_INSET_DECOR這兩個(gè)常量作用,直接看接口注解就可以一目了然了坯临。
FLAG_LAYOUT_IN_SCREEN:窗口標(biāo)志:將窗口置于整個(gè)屏幕中焊唬,忽略邊框周圍的裝飾(例如狀態(tài)欄)。窗口必須正確放置其內(nèi)容才能考慮屏幕裝飾看靠。如{@link Window#setFlags}中所述求晶,通常由Window為您設(shè)置此標(biāo)志。
FLAG_LAYOUT_INSET_DECOR:窗口標(biāo)志:僅與{@link #FLAG_LAYOUT_IN_SCREEN}結(jié)合使用的特殊選項(xiàng)衷笋。在請(qǐng)求布局時(shí)屏幕上的窗口可能會(huì)出現(xiàn)在屏幕裝飾的上方或下方例如狀態(tài)欄芳杏。通過還包括此標(biāo)志,窗口經(jīng)理將報(bào)告所需的插入矩形辟宗,以確保屏幕裝飾不覆蓋您的內(nèi)容爵赵。該標(biāo)志通常是*按照{(diào)@link Window#setFlags}中的說明,由Window為您設(shè)置泊脐。
理解:在保證開發(fā)者視圖完整性的情況下空幻,才會(huì)去考慮布局問題。當(dāng)兩個(gè)flag結(jié)合一起使用的話容客,就能看到我最終想要的效果圖:statusBar在頂部占一定高度秕铛,我自己的視圖在StatusBar下面進(jìn)行展示约郁,兩者相互不重合,不影響對(duì)方但两。如果mIsFloating為true的話鬓梅,那系統(tǒng)就只能讓開發(fā)者的視圖大小即為wrap_content(開發(fā)者視圖的xml的根布局為match_parent都沒有,因?yàn)檠b載開發(fā)者視圖的容器是wrap_content)谨湘,所以就算我按照staturBarColor的說明來使用绽快,設(shè)置各種flag值,依舊statusBar沒有反應(yīng)紧阔。
- 為什么上面說狀態(tài)欄是裝飾物坊罢?帶著這個(gè)疑問氮惯,我們繼續(xù)深入學(xué)習(xí)蝌数。
- 首先我們要找到statusBar怎么創(chuàng)建出來的,我愣是在PhoneWindow找呀找呀找嫂丙,都一直沒找到相關(guān)的乖仇。還好換下思路诱鞠,跟著staturBarColor去找,終于給我找到statusBar的代碼了这敬。
- 實(shí)際上我們?cè)谏鲜?strong>layoutResource含義圖解的圖片可以看到statusBar是以一個(gè)名為statusBarBackground的View形式出現(xiàn)的航夺,而且是和我們的layoutResource同一個(gè)層次。那么StatusBar就應(yīng)該是在DecorView中創(chuàng)建的拉崔涂,果不其然阳掐,我們?cè)贒ecorView里面找到我們要的代碼。
//DecorView的屬性值冷蚂,ColorViewState只是封裝一層數(shù)據(jù)結(jié)構(gòu)缭保,有興趣可以自己去看下源碼,不難的蝙茶,無非就是:View引用艺骂、設(shè)置位置參數(shù)、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是裝飾者呢?因?yàn)閷?duì)于PhoneWindow來說完全不關(guān)心這兩個(gè)View長什么樣子蹄衷,是否添加都不關(guān)心忧额,所以對(duì)于PhoneWindow來講statusBar和Navigation即是裝飾者。再來看看DecorView對(duì)兩個(gè)裝飾者如何操作愧口?
//DecorView的updateColorViewInt函數(shù)中
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;
//如果當(dāng)前Window的statusBar和Navigation還沒被創(chuàng)建睦番,先創(chuàng)建再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);
}
}
//下面就是過渡動(dòng)畫了
if (visibilityChanged) {
view.animate().cancel();
//省略部分代碼
……
} else {
view.setAlpha(1.0f);
view.setVisibility(showView ? VISIBLE : INVISIBLE);
}
}
//state所有屬性值均賦值完成
state.visible = show;
state.color = color;
}
- 經(jīng)過第五步的源碼分析,statusBar和Navigation已經(jīng)添加到我們的DecorView里面了。那剩下的問題就只有托嚣,添加了statusBar后我們的裝載開發(fā)者視圖的ViewGroup(不懂ViewGroup是什么的看Window標(biāo)題下的視圖顯示大概流程)如何增加自己的top和bottom的間距巩检,讓開發(fā)者視圖不與statusBar和Navigation重疊呢?
//DecorView的updateColorViews函數(shù)里
WindowInsets updateColorViews(WindowInsets insets, boolean animate) {
//省略部分代碼
……
//根據(jù)window or view的flag值示启,算出Navigation狀態(tài)值兢哭,是否消費(fèi)部分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;
//根據(jù)window or view的flag值,算出statusBar狀態(tài)值丑搔,是否消費(fèi)部分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);
//根據(jù)上面的參數(shù),計(jì)算出staturBar高度提揍,Navigation的高度和左右邊距
int consumedTop = consumingStatusBar ? mLastTopInset : 0;
int consumedRight = consumingNavBar ? mLastRightInset : 0;
int consumedBottom = consumingNavBar ? mLastBottomInset : 0;
int consumedLeft = consumingNavBar ? mLastLeftInset : 0;
//下面就是純粹的根據(jù)if else來改變裝載開發(fā)者視圖的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的部分區(qū)域啤月,那就要告訴PhoneWindow呀,把消耗區(qū)域值設(shè)置到insets里面(WindowInsets可自行查資料)
insets = insets.inset(consumedLeft, consumedTop, consumedRight, consumedBottom);
}
//省略部分代碼
……
if (insets != null) {
//告訴PhoneWindow我把你該部分區(qū)域已經(jīng)消耗了劳跃,不可分配給別人了谎仲。
insets = insets.consumeStableInsets();
}
return insets;
}
}
總結(jié)
上面說那么多,還沒提及怎么解決刨仑?那最后來個(gè)總結(jié)吧郑诺。既然知道windowIsFloating為true時(shí)候,是不添加statusBar和Navigation的杉武,那么我換成false即可辙诞。僅僅一個(gè)簡單style屬性類型,可以讓我們從PhoneWindow -> DecorView創(chuàng)建一路看過來轻抱,在遇到問題時(shí)候從源碼下手還是能學(xué)習(xí)到很多不錯(cuò)知識(shí)點(diǎn)的飞涂。
tips:當(dāng)你的DialogFragment為loading樣式、彈窗這些就不需要statusBar和Navigation了祈搜,可以直接設(shè)置為true较店。