1.什么是WindowInsets?
在Android源码的注释中解释为 window content 的一系列插入集合,final 型,不可修改,但后期可能继续扩展。
其主要成员包括 mSystemWindowInsets, mWindowDecorInsets, mStableInsets。
- mSystemWindowInsets
表示全窗口下,被StatusBar, NavigationBar, IME 或者其它系统窗口部分或者全部覆盖的区域。
- mWindowDecorInsets
表示内容窗口下,被Android FrameWork提供的窗体,诸如ActionBar, TitleBar, ToolBar,部分或全部覆盖区域。
- mStableInsets
表示全窗口下,被系统UI部分或者全部覆盖的区域。
2,如何理解WindowInsets
以 mSystemWindowInsets 为例:
@Deprecated
public int getSystemWindowInsetLeft() {
return getSystemWindowInsets().left;
}
/**
* Returns the top system window inset in pixels.
*
* <p>The system window inset represents the area of a full-screen window that is
* partially or fully obscured by the status bar, navigation bar, IME or other system windows.
* </p>
*
* @return The top system window inset
* @deprecated Use {@link #getInsets(int)} with {@link Type#systemBars()}
* instead.
*/
@Deprecated
public int getSystemWindowInsetTop() {
return getSystemWindowInsets().top;
}
/**
* Returns the right system window inset in pixels.
*
* <p>The system window inset represents the area of a full-screen window that is
* partially or fully obscured by the status bar, navigation bar, IME or other system windows.
* </p>
*
* @return The right system window inset
* @deprecated Use {@link #getInsets(int)} with {@link Type#systemBars()}
* instead.
*/
@Deprecated
public int getSystemWindowInsetRight() {
return getSystemWindowInsets().right;
}
/**
* Returns the bottom system window inset in pixels.
*
* <p>The system window inset represents the area of a full-screen window that is
* partially or fully obscured by the status bar, navigation bar, IME or other system windows.
* </p>
*
* @return The bottom system window inset
* @deprecated Use {@link #getInsets(int)} with {@link Type#systemBars()}
* instead.
*/
@Deprecated
public int getSystemWindowInsetBottom() {
return getSystemWindowInsets().bottom;
}
这里的Rect的概念已经区别于View的Rect了,它的四个点已经不再表示围成矩形的坐标,而表示的是insets需要的左右的宽度,顶部和底部需要的高度。
3,WindowInsets的分发流程
WindowInset是window大小发生变化的时候传递给ViewRootImpl的,ViewrootImpl会储存该值,然后进入到performTraversals方法内
if (dispatchApplyInsets || mLastSystemUiVisibility !=
mAttachInfo.mSystemUiVisibility || mApplyInsetsRequested) {
mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
dispatchApplyInsets(host);
// We applied insets so force contentInsetsChanged to ensure the
// hierarchy is measured below.
dispatchApplyInsets = true;
}
mAttachInfo实际上是View类中的静态内部类AttachInfo类的对象
public void dispatchApplyInsets(View host) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "dispatchApplyInsets");
mApplyInsetsRequested = false;
WindowInsets insets = getWindowInsets(true /* forceConstruct */);
if (!shouldDispatchCutout()) {
// Window is either not laid out in cutout or the status bar inset takes care of
// clearing the cutout, so we don't need to dispatch the cutout to the hierarchy.
insets = insets.consumeDisplayCutout();
}
host.dispatchApplyWindowInsets(insets);
mAttachInfo.delayNotifyContentCaptureInsetsEvent(insets.getInsets(Type.all()));
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
Insets:WindowInsets{
statusBars=Insets{left=0, top=0, right=0, bottom=0} max=null vis=false
navigationBars=Insets{left=0, top=0, right=0, bottom=0} max=null vis=false
=Insets{left=0, top=137, right=0, bottom=0} max=Insets{left=0, top=137, right=0, bottom=0} vis=true
ime=Insets{left=0, top=0, right=0, bottom=0} max=null vis=false
systemGestures=Insets{left=0, top=0, right=0, bottom=0} max=Insets{left=0, top=0, right=0, bottom=0} vis=true
mandatorySystemGestures=Insets{left=0, top=0, right=0, bottom=0} max=Insets{left=0, top=0, right=0, bottom=0} vis=true
tappableElement=Insets{left=0, top=0, right=0, bottom=0} max=Insets{left=0, top=0, right=0, bottom=0} vis=true
displayCutout=Insets{left=0, top=0, right=0, bottom=0} max=Insets{left=0, top=0, right=0, bottom=0} vis=true
windowDecor=null max=null vis=false
}
@Override
public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
insets = super.dispatchApplyWindowInsets(insets);
if (insets.isConsumed()) {
return insets;
}
if (View.sBrokenInsetsDispatch) {
return brokenDispatchApplyWindowInsets(insets);
} else {
return newDispatchApplyWindowInsets(insets);
}
}
先进行分发ViewGroup brokenDispatchApplyWindowInset
private WindowInsets brokenDispatchApplyWindowInsets(WindowInsets insets) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
insets = getChildAt(i).dispatchApplyWindowInsets(insets);
if (insets.isConsumed()) {
break;
}
}
return insets;
}
依次遍历
onApplyFrameworkOptionalFitSystemWindows:11391, View (android.view) onApplyWindowInsets:11372, View (android.view) dispatchApplyWindowInsets:11439, View (android.view) dispatchApplyWindowInsets:7391, ViewGroup (android.view) brokenDispatchApplyWindowInsets:7405, ViewGroup (android.view) dispatchApplyWindowInsets:7396, ViewGroup (android.view) brokenDispatchApplyWindowInsets:7405, ViewGroup (android.view) dispatchApplyWindowInsets:7396, ViewGroup (android.view) dispatchApplyInsets:2406, ViewRootImpl (android.view) performTraversals:2814, ViewRootImpl (android.view)
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方法。
如果无listener的话,就交给View本身的onApplyWindowInsets。一般的view都没有listener,CoordinatorLayout和AppBarLayout 、CollapsingToolbarLayout是存在listener的,而且CollapsingToolbarLayout是必然会消费掉WindowInsets的,CoordinatorLayout和AppBarLayout不消费WindowInsets。普通的view肯定调用的是本身的onApplyWindowInsets。
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
if ((mPrivateFlags4 & PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS) != 0
&& (mViewFlags & FITS_SYSTEM_WINDOWS) != 0) {
return onApplyFrameworkOptionalFitSystemWindows(insets);
}
if ((mPrivateFlags3 & PFLAG3_FITTING_SYSTEM_WINDOWS) == 0) {
// We weren't called from within a direct call to fitSystemWindows,
// call into it as a fallback in case we're in a class that overrides it
// and has logic to perform.
if (fitSystemWindows(insets.getSystemWindowInsetsAsRect())) {
return insets.consumeSystemWindowInsets();
}
} else {
// We were called from within a direct call to fitSystemWindows.
if (fitSystemWindowsInt(insets.getSystemWindowInsetsAsRect())) {
return insets.consumeSystemWindowInsets();
}
}
return insets;
}
PFLAG3_FITTING_SYSTEM_WINDOWS标记表示正在处理WindowInsets,在第一次调用fitSystemWindows方法后,mPrivateFlags3 的 PFLAG3_FITTING_SYSTEM_WINDOWS标志为被置位1了,所以进入fitSystemWindowsInt方法。
PFLAG3_FITTING_SYSTEM_WINDOWS==0表示没有正在处理的WindowInsets
如果当前没有正在处理WindowInsets就呼叫fitSystemWindows方法进行处理
否则(PFLAG3_FITTING_SYSTEM_WINDOWS!=0)呼叫fitSystemWindowsInt方法直接进行相应的逻辑处理(internalSetPadding)
如果这两个方法结果返回true,表示消费了WindowInsets
简单的认为onApplyWindowInsets就是调用fitSystemWindowsInt,而fitSystemWindowsInt就是调computeFitSystemWindows和internalSetPadding。computeFitSystemWindows是计算padding,而internalSetPadding就正式设置padding,padding设置好了,子view就会小一些,被约束在padding里面。注意一点fitSystemWindowsInt只有FITS_SYSTEM_WINDOWS这个flag为true才会进去,flag不对直接返回false。
@Deprecated
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;
}
// If we're not in the process of dispatching the newer apply insets call,
// that means we're not in the compatibility path. Dispatch into the newer
// apply insets path and take things from there.
try {
mPrivateFlags3 |= PFLAG3_FITTING_SYSTEM_WINDOWS;
return dispatchApplyWindowInsets(new WindowInsets(insets)).isConsumed();
} finally {
mPrivateFlags3 &= ~PFLAG3_FITTING_SYSTEM_WINDOWS;
}
} else {
// We're being called from the newer apply insets path.
// Perform the standard fallback behavior.
return fitSystemWindowsInt(insets);
}
}
这个方法是之前代码的逻辑,调用这个方法是为了保证基于之前版本开发的逻辑能够正常执行,首先判断PFLAG3_APPLYING_INSETS,这个标记表示当时正在执行WindowInsets的分发,
如果没有在分发(if ((mPrivateFlags3 & PFLAG3_APPLYING_INSETS) == 0)),那么针对windowInsets继续进行分发,并设定PFLAG3_FITTING_SYSTEM_WINDOWS,在View.onApplyWindowInsets方法内如果判断存在PFLAG3_FITTING_SYSTEM_WINDOWS标记,那么直接执行fitSystemWIndowInt方法
如果正在分发( flag& PFLAG3_APPLYING_INSETS !=0),那么直接执行else下一步的逻辑,进入fitSystemWindowsInt方法
//fitSystemWindows为true才会进行消费WindowInsets 否则直接返回false
private boolean fitSystemWindowsInt(Rect insets) {
if ((mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS) {
Rect localInsets = sThreadLocal.get();
//计算是否消费windowInsets
boolean res = computeFitSystemWindows(insets, localInsets);
//执行真正的Windowinsets消费逻辑 重新调整View的padding值
applyInsets(localInsets);
return res;
}
return false;
}
7
//计算是否消费windowInsets
protected boolean computeFitSystemWindows(Rect inoutInsets, Rect outLocalInsets) {
WindowInsets innerInsets = computeSystemWindowInsets(new WindowInsets(inoutInsets),
outLocalInsets);
inoutInsets.set(innerInsets.getSystemWindowInsetsAsRect());
return innerInsets.isSystemWindowInsetsConsumed();
}
public WindowInsets computeSystemWindowInsets(WindowInsets in, Rect outLocalInsets) {
boolean isOptionalFitSystemWindows = (mViewFlags & OPTIONAL_FITS_SYSTEM_WINDOWS) != 0
|| (mPrivateFlags4 & PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS) != 0;
if (isOptionalFitSystemWindows && mAttachInfo != null) {
OnContentApplyWindowInsetsListener listener =
mAttachInfo.mContentOnApplyWindowInsetsListener;
if (listener == null) {
// The application wants to take care of fitting system window for
// the content.
outLocalInsets.setEmpty();
return in;
}
Pair<Insets, WindowInsets> result = listener.onContentApplyWindowInsets(this, in);
outLocalInsets.set(result.first.toRect());
return result.second;
} else {
outLocalInsets.set(in.getSystemWindowInsetsAsRect());
return in.consumeSystemWindowInsets().inset(outLocalInsets);
}
}
mAttachInfo为view绑定window的标志,View.AttachInfo 里面的信息,就是View和Window之间的信息。每一个被添加到窗口上的View我们都会看到有一个AttachInfo,比如我们看DecorView和Window的绑定,AttachInfo 会通过View的diapatchAttachedTowWindow分发给View。如果是一个ViewGroup 那么这个这个AttachInfo也会分发给所有子View,以引用的方式。
- isOptionalFitSystemWindows = (mViewFlags & OPTIONAL_FITS_SYSTEM_WINDOWS)!= 0
- mAttachInfo == null
- ((mAttachInfo.mSystemUiVisibility & SYSTEM_UI_LAYOUT_FLAGS) == 0 && !mAttachInfo.mOverscanRequested))
- 条件1,// OPTIONAL_FITS_SYSTEM_WINDOWS 代表着是系统View,(mViewFlags & OPTIONAL_FITS_SYSTEM_WINDOWS) == 0 代表是用户的UI,(mViewFlags & OPTIONAL_FITS_SYSTEM_WINDOWS) != 0代表着系统View。
- 条件2,这个值始终为false。
- 条件3,A:!mAttachInfo.mOverscanRequested始终为true。 B:还记得前面说的 SYSTEM_UI_LAYOUT_FLAGS标记吗?mAttachInfo.mSystemUiVisibility & SYSTEM_UI_LAYOUT_FLAGS这个标记可以调用View的setSystemUiVisibility方法来设置,默认为0.所以如果没有调用setSystemUiVisibility来更改Flag的话,DecoreView的直接子View会消费掉事件,事件就不会往下面传递了。
-
public void makeOptionalFitsSystemWindows() { setFlags(OPTIONAL_FITS_SYSTEM_WINDOWS, OPTIONAL_FITS_SYSTEM_WINDOWS); } private void installDecor() { mForceDecorInstall = false; if (mDecor == null) { mDecor = generateDecor(-1); mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); mDecor.setIsRootNamespace(true); if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) { mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); } } else { // 设置Window mDecor.setWindow(this); } if (mContentParent == null) { mContentParent = generateLayout(mDecor); <!--关键点1--> mDecor.makeOptionalFitsSystemWindows(); ... }
OPTIONAL_FITS_SYSTEM_WINDOWS是通过 makeOptionalFitsSystemWindows设置的,入口只在PhoneWindow中,通过mDecor.makeOptionalFitsSystemWindows()设置,在installDecor的时候,里面还未涉及用户view,所以通过mDecor.makeOptionalFitsSystemWindows标记的都是系统自己的View布局
-
private void applyInsets(Rect insets) { mUserPaddingStart = UNDEFINED_PADDING; mUserPaddingEnd = UNDEFINED_PADDING; mUserPaddingLeftInitial = insets.left; mUserPaddingRightInitial = insets.right; internalSetPadding(insets.left, insets.top, insets.right, insets.bottom); }
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(); invalidateOutline(); } }
一些总结:
- 并不是每次布局都会分发WindowInsets,只有当windowInsets发生变化的时候,ViewRootImpl才会主动进行分发
- 消费WindowInsets方法有两种,一个是主动的通过设置mOnApplyWindowInsetsListener来进行处理,另一个则是被动的通过onApplyWindowInsets方法进行处理(需要将fitSystemWIndows属性设置为true)
- viewGroup会尝试自己先进行处理,然后再进行分发