我很难理解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);
}
}
我们在新的CoordinatorLayout
或AppBarLayout
中看到相同的模式。
这是否与fitsSystemWindows
的文档完全相反?在最后一种情况下,它意味着在系统栏后面绘制。
但是,如果您希望FrameLayout
将自己绘制在状态栏后面,则将fitsSystemWindows
设置为true并不会起到作用,因为默认实现会执行最初记录的内容。您必须覆盖它并添加与其他提到的类相同的标志。我错过了什么吗?
答案 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 Banes,advised除非容器文档说要使用它,否则不要使用fitsSystemWindows
属性。这样做的原因是该标志的默认行为经常不符合您的期望。视频中对此有很好的解释。
但是您应该在fitSystemWindows
中使用哪些特殊布局?好吧,它们是fitsSystemWindows
,DrawerLayout
,CoordinatorLayout
和AppBarLayout
。这些布局会覆盖默认的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。
参考: