浅谈WindowInsets的分发

浅谈WindowInsets的分发

在fitsSystemWindows对CoordinatorLayout的影响曾经说过fitSystemWindow的作用。今天再来仔细梳理一下。
ViewRootImpl在performTraversals时会调dispatchApplyInsets,内调DecorView的dispatchApplyWindowInsets,进行WindowInsets的分发。
下面有2个重要的函数一个是ViewGroup的dispatchApplyWindowInsets,一个是View的dispatchApplyWindowInsets。从下面的代码可以看出分发WindowInsets跟事件分发是类似的,不停的往child分发,如果有人消费了WindowInsets,那么就结束这个分发过程。

//ViewGroup
  @Override
    public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
        insets = super.dispatchApplyWindowInsets(insets);
        if (!insets.isConsumed()) {
            final int count = getChildCount();
            for (int i = 0; i < count; i++) {
                insets = getChildAt(i).dispatchApplyWindowInsets(insets);
                if (insets.isConsumed()) {
                    break;
                }
            }
        }
        return insets;
    }
//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;
        }
    }

再仔细看看View的dispatchApplyWindowInsets,这里如果存在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 ((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.getSystemWindowInsets())) {
                return insets.consumeSystemWindowInsets();
            }
        } else {
            // We were called from within a direct call to fitSystemWindows.
            if (fitSystemWindowsInt(insets.getSystemWindowInsets())) {
                return insets.consumeSystemWindowInsets();
            }
        }
        return insets;
    }

先简单的认为onApplyWindowInsets就是调用fitSystemWindowsInt,而fitSystemWindowsInt就是调computeFitSystemWindows和internalSetPadding。computeFitSystemWindows是计算padding,而internalSetPadding就正式设置padding,padding设置好了,子view就会小一些,被约束在padding里面。注意一点fitSystemWindowsInt只有FITS_SYSTEM_WINDOWS这个flag为true才会进去,flag不对直接返回false。

    private boolean fitSystemWindowsInt(Rect insets) {
        if ((mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS) {
                ...
            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代码如下,这里的if条件我改了下,更容易理解。isUserview代表是user层的view,就是我们写在xml内的,而不是系统提供的。mAttachInfo.mSystemUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN==0代表非全屏。是userview或者非全屏,就会消费掉insets。

//View
    protected boolean computeFitSystemWindows(Rect inoutInsets, Rect outLocalInsets) {
        if  (isUserview||mAttachInfo.mSystemUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN==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;
        }
    }

简单的流程如下,是否消费在computeFitSystemWindows里面决定。

dispatchApplyWindowInsets-> onApplyWindowInsets-> fitSystemWindowsInt
->1. computeFitSystemWindows
  2. internalSetPadding

总结

  • 对于没有另设lisener的view,我们称为普通view。普通view能消费insets的view,等价于2条件,1、fitsSystemWindows。2、是userview或者attachInfo非全屏。
    fitsSystemWindows &&(是userview||attachInfo非全屏)

  • userRoot以上的view有ContentFrameLayout、FitWindowsLinearLayout、FrameLayout、LinearLayout、DecorView。这里LinearLayout和FitWindowsLinearLayout是fitsSystemWindows的。userview是否fitsSystemWindows就看xml里设置了。

  • 像CoordinatorLayout、AppBarLayout、CollapsingToolbarLayout这些有listener的要另外考虑,前2者不消费insets,后者必消费。

案例分析

我们再来分析上文中,CoordinatorLayout的android:fitsSystemWindows=”true”的情况,由上文的分析,我们知道此时attachInfo为全屏,所以普通view消费insets的条件为fitsSystemWindows&& userview,这样我们就直接跳过了系统view,找到了CoordinatorLayout,而CoordinatorLayout不消费insets,那继续往下,AppBarLayout也不消费,最后找到了CollapsingToolbarLayout,它会消费insets。

DecorView: dispatchApplyWindowInsets
->...
-> CoordinatorLayout: dispatchApplyWindowInsets
-> listener.onApplyWindowInsets 不消费
-> AppBarLayout: dispatchApplyWindowInsets
-> listener.onApplyWindowInsets 不消费
-> CollapsingToolbarLayout: dispatchApplyWindowInsets
->listener.onApplyWindowInsets
->消费掉

再分析CoordinatorLayout的android:fitsSystemWindows=”false”的情况,此时我们知道attachInfo非全屏,那只要满足fitsSystemWindows就可以消费,而LinearLayout就是第一个满足的对象,会消费掉insets。消费的意义就是LinearLayout设置了paddingTop为63,避开了状态栏,而LinearLayout的子viewFrameLayout会比LinearLayout少了63。

DecorView: dispatchApplyWindowInsets
-> LinearLayout: dispatchApplyWindowInsets
-> LinearLayout: computeFitSystemWindows
->消费掉

再来分析下SPRscroll工程下的PullCoordiVpActivity。如果YXPtrLayout写了fitsSystemWindows,那么PullCoordinatorLayout的高度就是1731,如果不写fitsSystemWindows,那么PullCoordinatorLayout的高度就是1794.YXPtrLayout的高度一直是1794。这是为什么?

此时PullCoordinatorLayout内写了android:fitsSystemWindows为true,那么attachInfo就是全屏,所以普通view消费insets的条件为fitsSystemWindows&& userview

我们看第一种情况,YXPtrLayout写了fitsSystemWindows,那么YXPtrLayout就会消费insets。YXPtrLayout内设padding,所以PullCoordinatorLayout的高度只有1731了。

如果YXPtrLayout不写fitsSystemWindows,那么insets继续往下传,CoordinatorLayout和AppBarLayout都不去消费,最后是CollapsingToolbarLayout来消费

其他

  • 普通的view的消费,必然会设置padding,但是你也可以像CollapsingToolbarLayout加上listener,虽然消费了,但也不设置padding
  • 如果CoordinatorLayout包括状态栏,那么他的子view如果也想占据状态栏的位置的话,必须写fitsSystemWindows,原理是CoordinatorLayout的measure过程。CollapsingToolbarLayout和CoordinatorLayout类似。

ref

https://my.oschina.net/KobeGong/blog/469059

你可能感兴趣的:(android)