当前位置: 首页 > 知识库问答 >
问题:

FitsSystemWindows到底是做什么的?

荆运诚
2023-03-14

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

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

现在,检查视图。javaclass我可以看到,当设置为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;
}

有了新的材料设计,有了新的类别,这些类别广泛地使用了这面旗帜,这就是混淆的地方。在许多资料来源中,fitsSystemWindows被称为将视图放置在系统栏后面的标志。看这里。

ViewCompat中的文档。javaforsetFitsSystemWindows说:

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

根据这一点,fitsystemwindows仅仅意味着将执行函数fitsystemwindows()?新的材质类似乎只是将其用于状态栏下的绘图。如果我们看一下抽屉布局。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个答案

沈翰
2023-03-14

简而言之,如果你想知道是否要使用fitsSystemWindows,有一个由Chris Banes(Android团队的开发人员)设计的Insetter库,它提供了一个比fitsSystemWindows更好的选择。有关更多详细信息,请参见下面的解释。

Android团队在2015年发表了一篇好文章——我为什么要安装SystemWindows?。它很好地解释了属性的默认行为,以及一些布局(如DroperLayout)是如何覆盖它的。

但是,那是2015年。早在2017年,在droidcon工作的克里斯·班斯就建议不要使用fitSystemWindows属性,除非容器留档说要使用它。原因是标志的默认行为通常不符合你的期望。视频中对此有很好的解释。

但是您应该在哪些特殊布局中使用fitsSystemWindows?嗯,它是抽屉布局协调布局AppBarLayout折叠工具栏布局。这些布局覆盖了默认的fitsSystemWindows行为,并以特殊的方式处理它,这在视频中也得到了很好的解释。这种对属性的不同解释有时会导致困惑和类似的问题。事实上,在droidcon London的另一段视频中,Chris Banes承认超载默认行为的决定是一个错误(伦敦会议的时间戳是13:10)。

好吧,如果fitSystemWindows不是最终的解决方案,应该使用什么?在2019年的另一篇文章中,克里斯·班斯提出了另一种解决方案,一些基于WindowInset应用编程接口的自定义布局属性。例如,如果您希望右下角的FAB从导航栏边距,您可以轻松配置它:

<com.google.android.material.floatingactionbutton.FloatingActionButton
  app:marginBottomSystemWindowInsets="@{true}"
  app:marginRightSystemWindowInsets="@{true}"
  ... />

解决方案使用定制的@BindingAdapters,一个用于填充,另一个用于边距。我上面提到的那篇文章很好地描述了这种逻辑。一些谷歌示例使用该解决方案,例如查看Owl android material应用程序BindingAdapters。kt。我只是将适配器代码复制到这里作为参考:

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

正如您所看到的,实现并不简单。正如我之前提到的,欢迎您使用Chris Banes的Insetter库,它提供了相同的功能,请参见insetter-dbx。

还要注意的是,自从androidx核心库的1.5.0版本以来,WindowInsetAPI将发生变化。例如insets.systemWindowInset变成了insets.getInset(Type.systemBars()或Type.ime())。有关详细信息,请参阅库留档和文章。

参考资料:

  • 我为什么要安装系统窗口
  • WindowInsets-布局的侦听器
  • 设置键盘动画(第1部分)
  • 成为窗户装配大师(droidcon London 2017)
  • 成为窗户装配大师(droidcon NYC 2017)
劳灵均
2023-03-14

它不会在系统栏后面绘制,而是在系统栏后面拉伸,以使用与系统栏相同的颜色对其着色,但如果有意义,它包含的视图会填充在状态栏中

夏建木
2023-03-14

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

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

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

 类似资料:
  • 问题内容: 这是我的代码: 这是我从解释器运行输出时的结果: 如果您注意到输出,则创建巨大的列表会消耗621.5 MB,而删除它只会释放152.6 MB。当我检查文档时,我发现以下语句: 因此,我猜想它并没有删除对象本身,而只是取消了绑定。 但是,它在解除绑定方面做了什么工作,从而释放了很大的空间(152.6 MB) 。有人可以痛苦地解释我在这里发生什么吗? 问题答案: Python是一种垃圾收集

  • 问题内容: 在Tour of Go网站的go 1.5发行之前的版本中,有一段代码看起来像这样。 输出看起来像这样: 令我困扰的是,将其删除后,该程序不再显示“世界”。 为什么呢?如何影响执行力? 问题答案: 注意: 从Go 1.5开始,将GOMAXPROCS设置为硬件内核数:golang.org/doc/go1.5#runtime,低于1.5之前的原始答案。 当您在未指定GOMAXPROCS环境变

  • 根据几个因素(包括OS/浏览器组合),WebDriver可能等待或不等待页面加载。在某些情况下,WebDriver可能会在页面完成加载或甚至开始加载之前返回控制 有人能解释一下在什么情况下WebDriver会在页面完成甚至开始加载之前返回控制吗?

  • 我有一个Spring Boot后端,我刚刚解决了从Angular frontend上传文件时的“ERR_CONNECTION_RESET”问题,方法是配置Tomcat属性。我想弄清楚它到底是做什么的。Tomcat文档对我来说并不明显: 对于中止的上载,Tomcat将吞下的最大请求主体字节数(不包括传输编码开销)。中止上载是指Tomcat知道请求正文将被忽略,但客户端仍然发送它。如果Tomcat没有

  • 问题内容: 我听说过很多有关Spring的信息,人们在网络上都说Spring是Web开发的良好框架。Spring Framework到底是做什么用的? 问题答案: 基本上,Spring是用于依赖项注入的框架,该框架是一种允许构建高度分离的系统的模式。 问题 例如,假设你需要列出系统的用户,从而声明一个名为的接口: 也许是一个访问数据库以获取所有用户的实现: 在你看来,你需要访问一个实例(仅作为示例