android /system,android:fitSystemWindows详解
從Android 4.4開始,Android系統加入了一個比較酷的功能,就是我們可以設置狀態欄的的顏色了,有個這個功能,狀態欄就不再是黑乎乎的了,我們就可以根據我們應用的主色去設置狀態欄的顏色,使得應用體驗變得好一些,所以我們通過如下方式設置狀態欄透明。
window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,?WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);?window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION,?WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
但是設置了狀態欄和導航欄透明之后,發現Activity的contentView超出了ActionBar,那么我們就要使用fitSystemWindws來解決這個問題,關于具體如何解決這個問題,在Android4.4新的特性,在應用內開啟透明狀態欄和透明虛擬按鈕這篇博客中有詳細介紹。
那么android:fitSystemWindows到底是什么東西啊,它是怎樣計算的?
在Android Framework的源代碼中查看View.java,有這幾個重要方法,如下:
dispatchApplyWindowInsets
onApplyWindowInsets
fitSystemWindows
是一個自定義的View,并且覆蓋View的這三個方法,打印出Log,可以發現這三個方法的調用順序是
dispatchApplyWindowInsets
onApplyWindowInsets
fitSystemWindows
這里我們通過代碼調試的方法來查看Android Framework的方法調用堆棧,如圖:
根據堆棧可以發現,dispatchApplyWindowInsets方法是在ViewRootImpl.performMeasure(int,int)方法中調用的。也就是說,dispatchApplyWindowInsets是在整個View Hierarchy的measure過程中調用的。
在從layout文件中解釋到fitSystemWindows為true設置標志位。那這個標志為在什么時候起的作用。
case?com.android.internal.R.styleable.View_fitsSystemWindows:
if?(a.getBoolean(attr,?false))?{
viewFlagValues?|=?FITS_SYSTEM_WINDOWS;viewFlagMasks?|=?FITS_SYSTEM_WINDOWS;}break;
再看上面那幅堆棧圖,在ActionBarOverlayLayout.measure()中開始調用fitSystemWindows的相關方法,進入ActionBarOverlayLayout的源碼。
protected?void?onMeasure(int?widthMeasureSpec,?int?heightMeasureSpec)?{//通過findViewById方法,得到布局文件中的viewpullChildren();int?maxHeight?=?0;int?maxWidth?=?0;int?childState?=?0;int?topInset?=?0;int?bottomInset?=?0;//測量ActionBar的高度和寬度measureChildWithMargins(mActionBarTop,?widthMeasureSpec,?0,?heightMeasureSpec,?0);
LayoutParams?lp?=?(LayoutParams)?mActionBarTop.getLayoutParams();
maxWidth?=?Math.max(maxWidth,
mActionBarTop.getMeasuredWidth()?+?lp.leftMargin?+?lp.rightMargin);
maxHeight?=?Math.max(maxHeight,
mActionBarTop.getMeasuredHeight()?+?lp.topMargin?+?lp.bottomMargin);
childState?=?ViewUtils.combineMeasuredStates(childState,
ViewCompat.getMeasuredState(mActionBarTop));//?xlarge?screen?layout?doesn't?have?bottom?action?bar.//測量ActionBar底部區域的高度和寬度if?(mActionBarBottom?!=?null)?{
measureChildWithMargins(mActionBarBottom,?widthMeasureSpec,?0,?heightMeasureSpec,?0);
lp?=?(LayoutParams)?mActionBarBottom.getLayoutParams();
maxWidth?=?Math.max(maxWidth,
mActionBarBottom.getMeasuredWidth()?+?lp.leftMargin?+?lp.rightMargin);
maxHeight?=?Math.max(maxHeight,
mActionBarBottom.getMeasuredHeight()?+?lp.topMargin?+?lp.bottomMargin);
childState?=?ViewUtils.combineMeasuredStates(childState,
ViewCompat.getMeasuredState(mActionBarBottom));
}final?int?vis?=?ViewCompat.getWindowSystemUiVisibility(this);final?boolean?stable?=?(vis?&?SYSTEM_UI_FLAG_LAYOUT_STABLE)?!=?0;if?(stable)?{//?This?is?the?standard?space?needed?for?the?action?bar.?For?stable?measurement,//?we?can't?depend?on?the?size?currently?reported?by?it?--?this?must?remain?constant.topInset?=?mActionBarHeight;//考慮到ActionbarTab的高度,計算topInsetif?(mHasNonEmbeddedTabs)?{final?View?tabs?=?mActionBarTop.getTabContainer();if?(tabs?!=?null)?{//?If?tabs?are?not?embedded,?increase?space?on?top?to?account?for?them.topInset?+=?mActionBarHeight;
}
}
}?else?if?(mActionBarTop.getVisibility()?!=?GONE)?{//?This?is?the?space?needed?on?top?of?the?window?for?all?of?the?action?bar//?and?tabs.topInset?=?mActionBarTop.getMeasuredHeight();
}//如果ActionBar是split模式,考慮底部的高度,計算insetBottomif?(mDecorToolbar.isSplit())?{//?If?action?bar?is?split,?adjust?bottom?insets?for?it.if?(mActionBarBottom?!=?null)?{if?(stable)?{
bottomInset?=?mActionBarHeight;
}?else?{
bottomInset?=?mActionBarBottom.getMeasuredHeight();
}
}
}//?If?the?window?has?not?requested?system?UI?layout?flags,?we?need?to//?make?sure?its?content?is?not?being?covered?by?system?UI...?though?it//?will?still?be?covered?by?the?action?bar?if?they?have?requested?it?to//?overlay.mContentInsets.set(mBaseContentInsets);
mInnerInsets.set(mBaseInnerInsets);if?(!mOverlayMode?&&?!stable)?{
mContentInsets.top?+=?topInset;
mContentInsets.bottom?+=?bottomInset;
}?else?{
mInnerInsets.top?+=?topInset;
mInnerInsets.bottom?+=?bottomInset;
}//在ActionBar為非Overlay模式下,應用計算好的ContentInsetsapplyInsets(mContent,?mContentInsets,?true,?true,?true,?true);//if?(!mLastInnerInsets.equals(mInnerInsets))?{//?If?the?inner?insets?have?changed,?we?need?to?dispatch?this?down?to//?the?app's?fitSystemWindows().?We?do?this?before?measuring?the?content//?view?to?keep?the?same?semantics?as?the?normal?fitSystemWindows()?call.mLastInnerInsets.set(mInnerInsets);//Overlay模式下,將InnerInsets分發到子View中。?mContent.dispatchFitSystemWindows(mInnerInsets);}
measureChildWithMargins(mContent,?widthMeasureSpec,?0,?heightMeasureSpec,?0);
lp?=?(LayoutParams)?mContent.getLayoutParams();
maxWidth?=?Math.max(maxWidth,
mContent.getMeasuredWidth()?+?lp.leftMargin?+?lp.rightMargin);
maxHeight?=?Math.max(maxHeight,
mContent.getMeasuredHeight()?+?lp.topMargin?+?lp.bottomMargin);
childState?=?ViewUtils.combineMeasuredStates(childState,
ViewCompat.getMeasuredState(mContent));//?Account?for?padding?toomaxWidth?+=?getPaddingLeft()?+?getPaddingRight();
maxHeight?+=?getPaddingTop()?+?getPaddingBottom();//?Check?against?our?minimum?height?and?widthmaxHeight?=?Math.max(maxHeight,?getSuggestedMinimumHeight());
maxWidth?=?Math.max(maxWidth,?getSuggestedMinimumWidth());
setMeasuredDimension(
ViewCompat.resolveSizeAndState(maxWidth,?widthMeasureSpec,?childState),
ViewCompat.resolveSizeAndState(maxHeight,?heightMeasureSpec,
childState?<
}
根據上面的堆棧圖知道mContent是NativeActionModeAwareLayout類型,而NativeActionModeAwareLayout,沒有dispatchFitSystemWindows方法,那么查看其父類的dispatchFitSystemWindows方法。NativeActionModeAwareLayout的父類是ContentFrameLayout類型,看它的dispatchFitSystemWindows方法。
public?void?dispatchFitSystemWindows(Rect?insets)?{
fitSystemWindows(insets);
}
它直接調用View的fitSystemWindows方法。看View的fitSystemWindows方法。
protected?boolean?fitSystemWindows(Rect?insets)?{if?((mPrivateFlags3?&?PFLAG3_APPLYING_INSETS)?==?0)?{if?(insets?==?null)?{//?Null?insets?by?definition?have?already?been?consumed.//?This?call?cannot?apply?insets?since?there?are?none?to?apply,//?so?return?false.return?false;
}try?{
mPrivateFlags3?|=?PFLAG3_FITTING_SYSTEM_WINDOWS;return?dispatchApplyWindowInsets(new?WindowInsets(insets)).isConsumed();
}?finally?{
mPrivateFlags3?&=?~PFLAG3_FITTING_SYSTEM_WINDOWS;
}
}?else?{return?fitSystemWindowsInt(insets);
}
}
由于第一次調用,這里的mPrivateFlags3 的PFLAG3_APPLYING_INSETS標志為不為1,所以進入if條件。進入dispatchApplyWindowInsets方法。并將mPrivateFlags3 的PFLAG3_APPLYING_INSETS標志為置為1。由于ContentFrameLayout繼承了Framelayout ,所以進入了ViewGroup的dispatchApplyWindowInsets方法。
@Overridepublic?WindowInsets?dispatchApplyWindowInsets(WindowInsets?insets)?{
insets?=?super.dispatchApplyWindowInsets(insets);if?(!insets.isConsumed())?{final?int?count?=?getChildCount();for?(int?i?=?0;?i?
insets?=?getChildAt(i).dispatchApplyWindowInsets(insets);if?(insets.isConsumed())?{break;
}
}
}return?insets;
}
首先調用了 super.dispatchApplyWindowInsets方法,也就是View的 dispatchApplyWindowInsets,
然后如果insets沒有被消費掉的話,分別調用每個view child的dispatchApplyWindowInsets方法,讓子view去消費它,如果子view消費了,那么到此結束。先執行 super.dispatchApplyWindowInsets方法。
下面是View.dispatchApplyWindowInsets方法。
public?WindowInsets?dispatchApplyWindowInsets(WindowInsets?insets)?{try?{
mPrivateFlags3?|=?PFLAG3_APPLYING_INSETS;if?(mListenerInfo?!=?null?&&?mListenerInfo.mOnApplyWindowInsetsListener?!=?null)?{return?mListenerInfo.mOnApplyWindowInsetsListener.onApplyWindowInsets(this,?insets);
}?else?{return?onApplyWindowInsets(insets);
}
}?finally?{
mPrivateFlags3?&=?~PFLAG3_APPLYING_INSETS;
}
}
在此方法中首先將mPrivateFlags3 的PFLAG3_APPLYING_INSETS標志位置為1,然后如果開發者設置了listener的話就調用listener,否則調用onApplyWindowInsets方法。
public?WindowInsets?onApplyWindowInsets(WindowInsets?insets)?{if?((mPrivateFlags3?&?PFLAG3_FITTING_SYSTEM_WINDOWS)?==?0)?{if?(fitSystemWindows(insets.getSystemWindowInsets()))?{return?insets.consumeSystemWindowInsets();
}
}?else?{if?(fitSystemWindowsInt(insets.getSystemWindowInsets()))?{return?insets.consumeSystemWindowInsets();
}
}return?insets;
}
在第一次調用fitSystemWindows方法后,mPrivateFlags3 得 PFLAG3_FITTING_SYSTEM_WINDOWS標志為被置位1了,所以進入fitSystemWindowsInt方法。
private?boolean?fitSystemWindowsInt(Rect?insets)?{if?((mViewFlags?&?FITS_SYSTEM_WINDOWS)?==?FITS_SYSTEM_WINDOWS)?{
mUserPaddingStart?=?UNDEFINED_PADDING;
mUserPaddingEnd?=?UNDEFINED_PADDING;
Rect?localInsets?=?sThreadLocal.get();if?(localInsets?==?null)?{
localInsets?=?new?Rect();
sThreadLocal.set(localInsets);
}
boolean?res?=?computeFitSystemWindows(insets,?localInsets);
mUserPaddingLeftInitial?=?localInsets.left;
mUserPaddingRightInitial?=?localInsets.right;
internalSetPadding(localInsets.left,?localInsets.top,
localInsets.right,?localInsets.bottom);return?res;
}return?false;
}
在這個方法中,兩個關鍵函數
computeFitSystemWindows
internalSetPadding
先看computeFitSystemWindows。官方解釋是,計算insets應該被此view消費掉還是繼續傳遞。
protected?boolean?computeFitSystemWindows(Rect?inoutInsets,?Rect?outLocalInsets)?{if?((mViewFlags?&?OPTIONAL_FITS_SYSTEM_WINDOWS)?==?0||?mAttachInfo?==?null||?((mAttachInfo.mSystemUiVisibility?&?SYSTEM_UI_LAYOUT_FLAGS)?==?0&&?!mAttachInfo.mOverscanRequested))?{
outLocalInsets.set(inoutInsets);
inoutInsets.set(0,?0,?0,?0);return?true;
}?else?{//?The?application?wants?to?take?care?of?fitting?system?window?for//?the?content...?however?we?still?need?to?take?care?of?any?overscan?here.final?Rect?overscan?=?mAttachInfo.mOverscanInsets;
outLocalInsets.set(overscan);
inoutInsets.left?-=?overscan.left;
inoutInsets.top?-=?overscan.top;
inoutInsets.right?-=?overscan.right;
inoutInsets.bottom?-=?overscan.bottom;return?false;
}
}
再看internalSetPadding
此方法是設置view的padding。
protected?void?internalSetPadding(int?left,?int?top,?int?right,?int?bottom)?{
mUserPaddingLeft?=?left;
mUserPaddingRight?=?right;
mUserPaddingBottom?=?bottom;final?int?viewFlags?=?mViewFlags;boolean?changed?=?false;//?Common?case?is?there?are?no?scroll?bars.if?((viewFlags?&?(SCROLLBARS_VERTICAL|SCROLLBARS_HORIZONTAL))?!=?0)?{if?((viewFlags?&?SCROLLBARS_VERTICAL)?!=?0)?{final?int?offset?=?(viewFlags?&?SCROLLBARS_INSET_MASK)?==?0??0?:?getVerticalScrollbarWidth();switch?(mVerticalScrollbarPosition)?{case?SCROLLBAR_POSITION_DEFAULT:if?(isLayoutRtl())?{
left?+=?offset;
}?else?{
right?+=?offset;
}break;case?SCROLLBAR_POSITION_RIGHT:
right?+=?offset;break;case?SCROLLBAR_POSITION_LEFT:
left?+=?offset;break;
}
}if?((viewFlags?&?SCROLLBARS_HORIZONTAL)?!=?0)?{
bottom?+=?(viewFlags?&?SCROLLBARS_INSET_MASK)?==?0??0?:?getHorizontalScrollbarHeight();
}
}if?(mPaddingLeft?!=?left)?{
changed?=?true;
mPaddingLeft?=?left;
}if?(mPaddingTop?!=?top)?{
changed?=?true;
mPaddingTop?=?top;
}if?(mPaddingRight?!=?right)?{
changed?=?true;
mPaddingRight?=?right;
}if?(mPaddingBottom?!=?bottom)?{
changed?=?true;
mPaddingBottom?=?bottom;
}if?(changed)?{
requestLayout();
}
}
總結
以上是生活随笔為你收集整理的android /system,android:fitSystemWindows详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计算机科学与技术的感性认识,对计算机科学
- 下一篇: jquery触发点击事件