WindowInsets的分发流程

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,以引用的方式。

  1. isOptionalFitSystemWindows = (mViewFlags & OPTIONAL_FITS_SYSTEM_WINDOWS)!= 0
  2. mAttachInfo == null
  3. ((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会尝试自己先进行处理,然后再进行分发
  • 24
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值