我很难理解fitsSystemWindows
的概念,因为根据视图,它做不同的事情。根据官方的留档,这是一个
用于根据系统窗口(如状态栏)调整视图布局的布尔内部属性。如果为true,则调整此视图的填充以为系统窗口留出空间。
现在,检查视图。java
class我可以看到,当设置为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中的文档。java
forsetFitsSystemWindows
说:
设置此视图是否应考虑系统屏幕装饰(如状态栏)并插入其内容;也就是说,控制是否将执行{@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);
}
}
我们在新的CoordinatorLayout
或AppBarLayout
中看到了相同的模式。
这不是与fitsSystemWindows
的留档方式完全相反吗?在最后一种情况下,它意味着在系统栏后面绘制。
但是,如果希望FrameLayout
将自身绘制在状态栏后面,则将fitsSystemWindows
设置为true不会起作用,因为默认实现会执行最初记录的操作。您必须重写它,并添加与其他提到的类相同的标志。我错过什么了吗?
简而言之,如果你想知道是否要使用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}"
... />
该解决方案使用定制的@BindingAdapter
s,一个用于填充,另一个用于边距。我上面提到的那篇文章很好地描述了这种逻辑。一些谷歌示例使用该解决方案,例如查看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())
。有关详细信息,请参阅库留档和文章。
参考资料:
它不会在系统栏后面绘制,而是在系统栏后面拉伸,以使用与系统栏相同的颜色对其着色,但如果有意义,它包含的视图会填充在状态栏中
系统窗口是系统绘制非交互式(在状态栏中)或交互式(在导航栏中)内容的屏幕部分。
大多数情况下,你的应用程序不需要在状态栏或导航栏下绘图,但如果需要,你需要确保互动元素(如按钮)不隐藏在它们下面。这就是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是一种垃圾收集
问题内容: 我听说过很多有关Spring的信息,人们在网络上都说Spring是Web开发的良好框架。Spring Framework到底是做什么用的? 问题答案: 基本上,Spring是用于依赖项注入的框架,该框架是一种允许构建高度分离的系统的模式。 问题 例如,假设你需要列出系统的用户,从而声明一个名为的接口: 也许是一个访问数据库以获取所有用户的实现: 在你看来,你需要访问一个实例(仅作为示例
@basic(optional=false)@column(nullable=false)@basic注释将属性标记为Java对象级别的非可选属性。第二个设置,列映射上的nullable=false,只负责生成NOT NULL数据库约束。Hibernate JPA实现在任何情况下都以相同的方式对待这两个选项,因此您不妨只使用其中一个注释来实现这一目的。 我很困惑。这是什么意思-属性或变量在Java
我很难理解流,以workcount为例,对于像Kafka这样的无限源,“sum”到底是做什么的? 我有点理解有时间窗的情况,因为它有开始和结束时间,对我来说就像一个“批次”,但如果没有时间窗, 什么是开始时间和结束时间
问题内容: 在Tour of Go网站的go 1.5发行之前的版本中,有一段代码看起来像这样。 输出看起来像这样: 令我困扰的是,将其删除后,该程序不再显示“世界”。 为什么呢?如何影响执行力? 问题答案: 注意: 从Go 1.5开始,将GOMAXPROCS设置为硬件内核数:golang.org/doc/go1.5#runtime,低于1.5之前的原始答案。 当您在未指定GOMAXPROCS环境变