fitsSystemWindows理解

fitsSystemWindows 的定义

Boolean internal attribute to adjust view layout based on system windows such as the status bar. If true, adjusts the padding of this view to leave space for the system windows. Will only take effect if this view is in a non-embedded activity.

这个一个boolean值的内部属性,让view可以根据系统窗口(如status bar)来调整自己的布局,如果值为true,就会调整view的paingding属性来给system windows留出空间。只有在非嵌入式的activity的view才有效果。

fitsSystemWindows 的作用

android:fitsSystemWindows="true" attribute gives you: it sets the padding of the View to ensure the contents don’t overlay the system windows.

设置View的padding,确定content不会与system windows重叠。

A few things to keep in mind:

  • fitsSystemWindows is applied depth first * — ordering matters: it’s the first View that consumes the insets that makes a difference
    Insets are always relative to the full window — insets may be applied even before layout happens, so don’t assume the default behavior knows anything about the position of a View when applying its padding
    Any other padding you’ve set is overwritten — you’ll note that paddingLeft /paddingTop /etc is ineffective if you are using android:fitsSystemWindows="true" on the same View

有几点是需要注意的:

  • 属性需要在root view设置,只有root view消费insets才会生效。
  • insets 是相对于全屏幕的。insets(边框)可能在 layout 之前(view生产之前)就已经设置, 所以insets的padding值,绝不会是相对于view的位置,而是相对于全屏幕。
  • 任何你设置的padding都会被覆盖。 在同一个view上面设置了 android:fitsSystemWindows="true" 的同时,还设置了 paddingLeft paddingTop 等等,后者不会生效。

如果想让RecycleView的内容滚动到状态栏之下, 可以同时设置android:fitsSystemWindows="true"和android:clipToPadding="false", 这样在布局初始化的时候,内容不会在状态栏之下, 滚动的时候, 内容可以滚到状态栏之下;
::android:clipToPadding="false"的作用是是让padding的位置也可以用来绘制, clipToPadding默认是true::

自定义fitsSystemWindows

On KitKat and below, your custom View could override fitSystemWindows()
and provide any functionality you wanted — just return true
if you’ve consumed the insets or false if you’d like to give other Views a chance.

在KitKat(4.4)或者4.4以下的版本,在自定义view中重写fitSystemWindows()方法,如果要消费insets则返回true , 返回false则让其他view去消费。

on Lollipop and higher devices, we provide some new APIs to make customizing this behavior much easier and consistent with other behaviors for Views. You’ll instead override onApplyWindowInsets(), which allows the View to consume as much or as little of the insets as you need and be able to call dispatchApplyWindowInsets() on child views as needed.

Lollipop(5.0) 或者 5.0以上版本, 提供了新的API ,只要重写onApplyWindowInsets(),就能允许自定义view去消费任何大小的insets ,并且能调用dispatchApplyWindowInsets() 让子view接着消费insets。

you don’t even need to subclass your Views if you only need custom behavior on Lollipop and higher you can use ViewCompat.setOnApplyWindowInsetsListener(), which will be given preference over the View’s onApplyWindowInsets(). ViewCompat also provides helper methods for calling onApplyWindowInsets() and dispatchApplyWindowInsets() without version checking.

在Lollipop(5.0)或者5.0以上的版本,如果不想继承view的话,可以使用ViewCompat.setOnApplyWindowInsetsListener() , 这个方法优先于View.onApplyWindowInsets()执行。
ViewCompat 同时也提供了 onApplyWindowInsets() dispatchApplyWindowInsets() ,解决了兼容性的问题。

fitsSystemWindows 源码

根据FITS_SYSTEM_WINDOWS标志位,无论哪个版本,默认都是直接设置padding

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

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

fitsSystemWindows实例

系统的基本控件((FrameLayout, LinearLayout, 等)都使用默认的行为,Support 包中有些控件使用了自定义行为。
一个使用自定义行为的示例就是侧边栏,侧边栏打开的时候,内容是占满整个屏幕高度的,状态栏显示为透明的,下面是 侧边栏的内容。

fitsSystemWindows理解_第1张图片

这里 DrawerLayout 使用 fitsSystemWindows 来表明需要处理 insets,但是仍然使用状态栏的颜色来绘制状态栏背景(状态栏颜色为 主题的 colorPrimaryDark 所设置的颜色)。
然后 DrawerLayout 在每个子 View 上调用 dispatchApplyWindowInsets() 函数,这样 子 View 也有 机会处理 insets,这和系统默认行为是不一样的(系统默认行为只是吃掉这个 insets,然后子 View 无法继续处理)。
CoordinatorLayout 对此也做了特殊处理,让每个子 View 的 Behavior 可以根据系统窗口的大小来做不同的处理。 还使用 fitsSystemWindows 属性来判断是否需要绘制状态栏背景。
通用 CollapsingToolbarLayout 也根据 fitsSystemWindows 属性来确定何时何地绘制 内容上方的半透明背景。
在 cheesesquare 示例项目中演示了这些 fitsSystemWindows 使用场景,可以下载该示例项目查看如何使用的。

也可以参考这个项目:

https://github.com/Jude95/FitSystemWindowLayout

参考:
https://stackoverflow.com/questions/3355367/height-of-statusbar
http://blog.chengyunfeng.com/?p=905
https://stackoverflow.com/questions/28387289/fitsystemwindows-programmatically-for-status-bar-transparency
http://www.jianshu.com/p/f3683e27fd94
https://developer.android.com/reference/android/support/design/widget/AppBarLayout.html
https://medium.com/google-developers/why-would-i-want-to-fitssystemwindows-4e26d9ce1eec?linkId=19685562
https://github.com/hehonghui/android-tech-frontier/blob/master/issue-35/%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E4%BB%AC%E8%A6%81%E7%94%A8fitsSystemWindows.md

你可能感兴趣的:(fitsSystemWindows理解)