究竟什么适合系统窗口做什么?

时间:2015-08-01 10:56:21

标签: android android-layout

我很难理解fitsSystemWindows的概念,因为它取决于它做不同的事情。根据官方文档,这是一个

  

布尔内部属性,用于根据系统窗口(如状态栏)调整视图布局。如果为true,则将此视图的填充调整为为系统窗口留出空间

现在,检查View.java类我可以看到,当设置为true时,窗口插入(状态栏,导航栏...)将应用于视图填充,其工作原理如下:上面引用的文件。这是代码的相关部分:

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

使用新的Material设计,有一些新的类可以广泛使用这个标志,这就是混乱的来源。在许多来源中,fitsSystemWindows被提及作为设置在系统条后面放置视图的标志。请参阅here

ViewCompat.java setFitsSystemWindows中的文档说:

  

设置此视图是否应考虑系统屏幕装饰        如状态栏和插入其内容; 即控制是否        {@link View#fitSystemWindows(Rect)}的默认实现将是        执行。有关详细信息,请参阅该方法

根据这个,fitsSystemWindows只是意味着将执行函数fitsSystemWindows()?新的Material类似乎只是用于在状态栏下绘图。如果我们查看DrawerLayout.java的代码,我们可以看到:

if (ViewCompat.getFitsSystemWindows(this)) {
        IMPL.configureApplyInsets(this);
        mStatusBarBackground = IMPL.getDefaultStatusBarBackground(context);
    }

...

public static void configureApplyInsets(View drawerLayout) {
    if (drawerLayout instanceof DrawerLayoutImpl) {
        drawerLayout.setOnApplyWindowInsetsListener(new InsetsListener());
        drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
    }
}

我们在新的CoordinatorLayoutAppBarLayout中看到相同的模式。

这是否与fitsSystemWindows的文档完全相反?在最后一种情况下,它意味着在系统栏后面绘制

但是,如果您希望FrameLayout将自己绘制在状态栏后面,则将fitsSystemWindows设置为true并不会起到作用,因为默认实现会执行最初记录的内容。您必须覆盖它并添加与其他提到的类相同的标志。我错过了什么吗?

3 个答案:

答案 0 :(得分:12)

  

系统窗口是系统绘制的屏幕部分   非交互式(在状态栏的情况下)或交互式   (在导航栏的情况下)内容。

     

大多数情况下,您的应用无需在状态栏下绘图   导航栏,但如果你这样做:你需要确保交互式   元素(如按钮)不会隐藏在它们下面。那是什么的   android:fitsSystemWindows =“true”属性的默认行为   给你:它设置视图的填充以确保内容   不要覆盖系统窗口。

https://medium.com/google-developers/why-would-i-want-to-fitssystemwindows-4e26d9ce1eec

答案 1 :(得分:4)

它不会在系统栏后面绘制 它在条形图后面延伸,用它所具有的相同颜色着色,但它包含的视图填充在状态栏内 如果这是有道理的

答案 2 :(得分:1)

简而言之,如果您想弄清楚是否使用fitsSystemWindows,则可以使用Chris Banes(Android团队的开发人员)的Insetter库,它为{ {1}}。有关更多详细信息,请看下面的说明。

Android团队在2015年发表了一篇不错的文章-Why would I want to fitsSystemWindows?。很好地说明了该属性的默认行为以及诸如DrawerLayout之类的某些布局如何覆盖该属性。

但是,那是2015年。回到2017年,在Android系统上工作的droidcon Chris Banesadvised除非容器文档说要使用它,否则不要使用fitsSystemWindows属性。这样做的原因是该标志的默认行为经常不符合您的期望。视频中对此有很好的解释。

但是您应该在fitSystemWindows中使用哪些特殊布局?好吧,它们是fitsSystemWindowsDrawerLayoutCoordinatorLayoutAppBarLayout。这些布局会覆盖默认的CollapsingToolbarLayout行为,并以一种特殊的方式对其进行处理,这在视频中也得到了很好的解释。对属性的这种不同解释有时会引起混乱和类似此处的问题。实际上,在another video的droidcon伦敦中,克里斯·贝恩斯(Chris Banes)承认超载默认行为的决定是一个错误(伦敦会议的13:10时间戳)。

好吧,如果fitsSystemWindows不是最终解决方案,应该使用什么?克里斯·班纳斯(Chris Banes)在2019年的another article中提出了另一种解决方案,即基于WindowInsets API的一些自定义布局属性。例如,如果您想在导航栏的右下角添加FAB,则可以轻松对其进行配置:

fitSystemWindows

该解决方案使用自定义<com.google.android.material.floatingactionbutton.FloatingActionButton app:marginBottomSystemWindowInsets="@{true}" app:marginRightSystemWindowInsets="@{true}" ... /> ,一个用于填充,另一个用于边距。我在上面提到的article中对逻辑进行了很好的描述。一些Google样本使用该解决方案,例如,参见Owl android material app BindingAdapters.kt。我只是在这里复制适配器代码以供参考:

@BindingAdapter

如您所见,实现并不简单。如前所述,欢迎您使用Chris Banes提供的Insetter库,该库提供相同的功能,请参见insetter-dbx

还要注意,自androidx core库的1.5.0版本以来,WindowInsets API将会更改。例如,@BindingAdapter( "paddingLeftSystemWindowInsets", "paddingTopSystemWindowInsets", "paddingRightSystemWindowInsets", "paddingBottomSystemWindowInsets", requireAll = false ) fun View.applySystemWindowInsetsPadding( previousApplyLeft: Boolean, previousApplyTop: Boolean, previousApplyRight: Boolean, previousApplyBottom: Boolean, applyLeft: Boolean, applyTop: Boolean, applyRight: Boolean, applyBottom: Boolean ) { if (previousApplyLeft == applyLeft && previousApplyTop == applyTop && previousApplyRight == applyRight && previousApplyBottom == applyBottom ) { return } doOnApplyWindowInsets { view, insets, padding, _ -> val left = if (applyLeft) insets.systemWindowInsetLeft else 0 val top = if (applyTop) insets.systemWindowInsetTop else 0 val right = if (applyRight) insets.systemWindowInsetRight else 0 val bottom = if (applyBottom) insets.systemWindowInsetBottom else 0 view.setPadding( padding.left + left, padding.top + top, padding.right + right, padding.bottom + bottom ) } } @BindingAdapter( "marginLeftSystemWindowInsets", "marginTopSystemWindowInsets", "marginRightSystemWindowInsets", "marginBottomSystemWindowInsets", requireAll = false ) fun View.applySystemWindowInsetsMargin( previousApplyLeft: Boolean, previousApplyTop: Boolean, previousApplyRight: Boolean, previousApplyBottom: Boolean, applyLeft: Boolean, applyTop: Boolean, applyRight: Boolean, applyBottom: Boolean ) { if (previousApplyLeft == applyLeft && previousApplyTop == applyTop && previousApplyRight == applyRight && previousApplyBottom == applyBottom ) { return } doOnApplyWindowInsets { view, insets, _, margin -> val left = if (applyLeft) insets.systemWindowInsetLeft else 0 val top = if (applyTop) insets.systemWindowInsetTop else 0 val right = if (applyRight) insets.systemWindowInsetRight else 0 val bottom = if (applyBottom) insets.systemWindowInsetBottom else 0 view.updateLayoutParams<ViewGroup.MarginLayoutParams> { leftMargin = margin.left + left topMargin = margin.top + top rightMargin = margin.right + right bottomMargin = margin.bottom + bottom } } } fun View.doOnApplyWindowInsets( block: (View, WindowInsets, InitialPadding, InitialMargin) -> Unit ) { // Create a snapshot of the view's padding & margin states val initialPadding = recordInitialPaddingForView(this) val initialMargin = recordInitialMarginForView(this) // Set an actual OnApplyWindowInsetsListener which proxies to the given // lambda, also passing in the original padding & margin states setOnApplyWindowInsetsListener { v, insets -> block(v, insets, initialPadding, initialMargin) // Always return the insets, so that children can also use them insets } // request some insets requestApplyInsetsWhenAttached() } class InitialPadding(val left: Int, val top: Int, val right: Int, val bottom: Int) class InitialMargin(val left: Int, val top: Int, val right: Int, val bottom: Int) private fun recordInitialPaddingForView(view: View) = InitialPadding( view.paddingLeft, view.paddingTop, view.paddingRight, view.paddingBottom ) private fun recordInitialMarginForView(view: View): InitialMargin { val lp = view.layoutParams as? ViewGroup.MarginLayoutParams ?: throw IllegalArgumentException("Invalid view layout params") return InitialMargin(lp.leftMargin, lp.topMargin, lp.rightMargin, lp.bottomMargin) } fun View.requestApplyInsetsWhenAttached() { if (isAttachedToWindow) { // We're already attached, just request as normal requestApplyInsets() } else { // We're not attached to the hierarchy, add a listener to // request when we are addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener { override fun onViewAttachedToWindow(v: View) { v.removeOnAttachStateChangeListener(this) v.requestApplyInsets() } override fun onViewDetachedFromWindow(v: View) = Unit }) } } 变为insets.systemWindowInsets。有关更多详细信息,请参见库文档和article

参考: