10fitsSystemWindows对CoordinatorLayout的影响

10fitsSystemWindows对CoordinatorLayout的影响

提到过,为了让CollapsingToolbarLayout内部的伪状态栏和真正的statusbar重合, CoordinatorLayout和AppBarLayout的fitsSystemWindows应该一样,同时为true或者false。

现在来具体分析一下,各种case。对应Activity: CollapsFitSystemActivity

  • CoordinatorLayout和AppBarLayout的fitsSystemWindows都为true
    此时CoordinatorLayout包含状态栏,AppBarLayout也包含状态,CollapsingToolbarLayout内的伪状态栏和实际状态栏在同样位置。

  • CoordinatorLayout的fitsSystemWindows为true,AppBarLayout的fitsSystemWindows为false
    此时CoordinatorLayout包含状态栏,AppBarLayout不包含状态栏,会出现伪状态栏和实际状态栏不在一起,就显示了2条状态栏,

  • CoordinatorLayout的fitsSystemWindows为false,AppBarLayout的fitsSystemWindows为false
    此时CoordinatorLayout不包括状态栏,AppBarLayout不包含状态栏,伪状态栏不会绘制。

为何CoordinatorLayout的fitsSystemWindows为false,CoordinatorLayout就不包括状态栏

CoordinatorLayout的fitsSystemWindows

这个值设置为true或者false,会有什么意义呢?为true,CoordinatorLayout就包括状态栏,否则就不包括状态栏。简单一句话,背后有一大堆代码来实现这个。

影响attachInfo.mSystemUiVisibility

首先看CoordinatorLayout构造函数里面有下边这段代码,setupForWindowInsets这明显就是处理系统边界的。

//CoordinatorLayout构造函数
  if (INSETS_HELPER != null) {
            INSETS_HELPER.setupForWindowInsets(this, new ApplyInsetsListener());
        }

看INSETS_HELPER.setupForWindowInsets的具体实现,在CoordinatorLayoutInsetsHelperLollipop内,这里可以看出如果设置为true,那么就会加上View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN这个flag顾名思义代表全屏的意思。所以CoordinatorLayout 的mSystemUiVisibility内有SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN标志。

//CoordinatorLayoutInsetsHelperLollipop
  public void setupForWindowInsets(View view, OnApplyWindowInsetsListener insetsListener) {
        if (ViewCompat.getFitsSystemWindows(view)) {
            // First apply the insets listener
            ViewCompat.setOnApplyWindowInsetsListener(view, insetsListener);
            // Now set the sys ui flags to enable us to lay out in the window insets
            view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
        }
    }

而在view的performCollectViewAttributes代码里,把 attachInfo.mSystemUiVisibility |= mSystemUiVisibility;,所以attachInfo.mSystemUiVisibility里面也设置了SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN标志。attachInfo是所有view共享的,从ViewRootImpl dispatch下来。

    void performCollectViewAttributes(AttachInfo attachInfo, int visibility) {
        if ((visibility & VISIBILITY_MASK) == VISIBLE) {
            if ((mViewFlags & KEEP_SCREEN_ON) == KEEP_SCREEN_ON) {
                attachInfo.mKeepScreenOn = true;
            }
            attachInfo.mSystemUiVisibility |= mSystemUiVisibility;
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnSystemUiVisibilityChangeListener != null) {
                attachInfo.mHasSystemUiListeners = true;
            }
        }
    }

dispatchApplyInsets

DecorView的直接子view LinearLayout
ViewRootImpl在performTraversals时会调dispatchApplyInsets,内调DecorView的dispatchApplyWindowInsets,然后调DecorView的child的dispatchApplyWindowInsets,DecorView主要child是LinearLayout。所以会调用LinearLayout的dispatchApplyWindowInsets。LinearLayout的dispatchApplyWindowInsets会调用super.dispatchApplyWindowInsets。

//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的dispatchApplyWindowInsets,会走L7 onApplyWindowInsets

   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;
        }
    }

这里走L6 fitSystemWindows,然后内部调用fitSystemWindowsInt,

  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;
    }

fitSystemWindowsInt里主要看 computeFitSystemWindows和internalSetPadding。

//View
  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;
            //根据localInsets设置padding
            internalSetPadding(localInsets.left, localInsets.top,
                    localInsets.right, localInsets.bottom);
            return res;
        }
        return false;
    }

内调computeFitSystemWindows(此时this为DecorView的子view LinearLayout),看这里,这里的if有3个条件,我们可以简单点无视前面2个条件,这样就变为

((mAttachInfo.mSystemUiVisibility & SYSTEM_UI_LAYOUT_FLAGS) == 0
&& !mAttachInfo.mOverscanRequested)

其实!mAttachInfo.mOverscanRequested一般为true,所以进一步简化条件变为(mAttachInfo.mSystemUiVisibility & SYSTEM_UI_LAYOUT_FLAGS) == 0

再看

    public static final int SYSTEM_UI_LAYOUT_FLAGS =
            SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
            | SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;

SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION肯定是false,所以我们只要看SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN就好了,条件进一步简化
mAttachInfo.mSystemUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN==0
而由上文知,如果fitsSystemWindows 设置为true的话,mAttachInfo.mSystemUiVisibility 内会设置SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN,所以此时if不满足走下边else。
如果fitsSystemWindows设置为false,那就走if,把 outLocalInsets.set(inoutInsets);,这样上边的localInsets.top就是63(状态栏高度),在internalSetPadding内会设置LinearLayout的paddingTop为63,这样CoordinatorLayout就不可能包括状态栏了。
而如果CoordinatorLayout的fitsSystemWindows为true,那么下边会走else, LinearLayout的paddingTop也为0,所以CoordinatorLayout就包括状态栏了。

    /**
     * @hide Compute the insets that should be consumed by this view and the ones
     * that should propagate to those under it.
     */
    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)) {
            //CoordinatorLayout的fitsSystemWindows为false
            outLocalInsets.set(inoutInsets);
            inoutInsets.set(0, 0, 0, 0);
            return true;
        } else {
        //CoordinatorLayout的fitsSystemWindows为true
            // 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;
        }
    }

fitsSystemWindows设为true和false的区别主要在于DecorView的子view LinearLayout的padding设置不同。所以第三层的FrameLayout的高度就不一样了。这就是CoordinatorLayout是否包含状态栏的原因。

为何CoordinatorLayout的fitsSystemWindows为false,AppBarLayout的fitsSystemWindows为false,不会出现双状态栏呢?

首先我们要看看CollapsingToolbarLayout的伪状态栏绘制有什么前提,必须topInset>0, mLastInsets不为null。那mLastInsets是什么时候赋值的呢?

//android.support.design.widget.CollapsingToolbarLayout#draw
    // Now draw the status bar scrim
        if (mStatusBarScrim != null && mScrimAlpha > 0) {
            final int topInset = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0;
            if (topInset > 0) {
                mStatusBarScrim.setBounds(0, -mCurrentOffset, getWidth(),
                        topInset - mCurrentOffset);
                mStatusBarScrim.mutate().setAlpha(mScrimAlpha);
                mStatusBarScrim.draw(canvas);
            }
        }

构造函数里注册了个回调,这是在dispatchApplyInsets调用的,必须调到CollapsingToolbarLayout的dispatchApplyInsets才可能有mLastInsets。

//CollapsingToolbarLayout构造函数
        ViewCompat.setOnApplyWindowInsetsListener(this,
                new android.support.v4.view.OnApplyWindowInsetsListener() {
                    @Override
                    public WindowInsetsCompat onApplyWindowInsets(View v,
                            WindowInsetsCompat insets) {
                        mLastInsets = insets;
                        requestLayout();
                        return insets.consumeSystemWindowInsets();
                    }
                });

在ViewRootImpl的dispatchApplyInsets过程中,会发给DecorView的直接子view,LinearLayout,如果CoordinatorLayout的fitsSystemWindows为false,那么 LinearLayout在L3会消耗掉这个inset,直接返回true,而不会继续往
LinearLayout的子view分发,所以CollapsingToolbarLayout内的mLastInsets必定为空,那伪状态栏根本不会绘制。

    @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;
    }

总结

1、CoordinatorLayout的fitsSystemWindows为false,那CollapsingToolbarLayout就必定不会绘制伪状态栏,因为mLastInsets为null。

参考资料

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

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: android:fitsSystemWindows是一个布局属性,用于指定布局是否需要考虑系统窗口的影响。当设置为true时,布局会被调整以适应系统窗口的边界,例如状态栏和导航栏。这个属性通常用于全屏模式下的布局,以确保布局不会被系统窗口遮挡。 ### 回答2: android:fitsSystemWindows是一个可以用来配置View的属性。它的作用是告诉View是否要改变自己的尺寸来适应系统窗口的大小变化。系统窗口可以是状态栏、导航栏等。当设置android:fitsSystemWindows为true时,View会自动调整自己的尺寸,使其内容不被系统窗口遮挡。当设置为false时,View会忽略系统窗口的大小变化,保持原有的尺寸。 使用android:fitsSystemWindows属性可以在设计界面时考虑到系统窗口的变化,确保内容能够完整的展示在屏幕上。一般情况下,顶部的状态栏与底部的导航栏会占据一部分屏幕空间,如果不适配这些系统窗口,可能会导致View的内容被遮挡或者布局不合理。 需要注意的是,android:fitsSystemWindows属性只会影响到直接包含该属性的View,而不会影响其子View。如果需要对所有子View都进行适配,可以在父View中设置android:fitsSystemWindows属性为true。 总结来说,android:fitsSystemWindows属性的主要作用是用来适应系统窗口的大小变化,确保View的内容能够完整显示在屏幕上。在设计界面时,可以根据实际需求灵活配置该属性,以获得更好的用户体验。 ### 回答3: android:fitsSystemWindows是一个针对Android应用程序窗口的布局属性,用于指定窗口内容是否需要适应系统窗口区域。 在Android系统中,系统窗口区域指的是屏幕上的状态栏(StatusBar)和导航栏(NavigationBar)等系统UI元素所占据的区域。默认情况下,应用程序的内容会延伸到系统窗口区域内部,但有些时候我们可能希望应用程序的布局能够适应系统窗口区域的变化。 通过在layout文件中使用android:fitsSystemWindows属性,可以控制应用窗口的布局是否考虑系统窗口区域。当android:fitsSystemWindows属性被设置为true时,表示应用程序的内容会被适应系统窗口区域,即内容将不会延伸至系统窗口区域内部。而当android:fitsSystemWindows属性被设置为false时,表示应用程序的内容不会适应系统窗口区域,即内容会延伸至系统窗口区域内部。 通过使用android:fitsSystemWindows属性,我们可以灵活地控制应用程序窗口的布局。例如,当我们希望应用程序的内容不被状态栏遮挡时,可以将android:fitsSystemWindows属性设置为true,使得内容适应状态栏所占据的区域,从而避免内容被状态栏遮挡。 需要注意的是,android:fitsSystemWindows属性只有在应用程序的主题中设置了android:windowTranslucentStatus或android:windowTranslucentNavigation属性时才会生效。这两个属性用于设置状态栏或导航栏的背景是否透明,如果没有设置这两个属性,即使设置了android:fitsSystemWindows为true,应用程序的内容仍然会延伸至系统窗口区域内部。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值