Pin*_*Pin 113 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;
}
Run Code Online (Sandbox Code Playgroud)
随着新材料设计的出现,有一些新的类广泛使用这个标志,这就是混乱的来源.在许多来源中fitsSystemWindows被提到作为设置在系统栏后面放置视图的标志.看到这里.
在文档ViewCompat.java的setFitsSystemWindows说:
设置此视图是否应考虑系统屏幕装饰,例如状态栏和插入其内容; 也就是说,控制是否执行{@link View#fitSystemWindows(Rect)}的默认实现.有关详细信息,请参阅该方法.
据此,fitsSystemWindows仅仅意味着该功能fitsSystemWindows()将被执行?新的Material类似乎只是用于在状态栏下绘图.如果我们查看DrawerLayout.java代码,我们可以看到:
if (ViewCompat.getFitsSystemWindows(this)) {
IMPL.configureApplyInsets(this);
mStatusBarBackground = IMPL.getDefaultStatusBarBackground(context);
}
Run Code Online (Sandbox Code Playgroud)
...
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);
}
}
Run Code Online (Sandbox Code Playgroud)
我们在新的CoordinatorLayout或者看到相同的模式AppBarLayout.
这不是与文档完全相反的方式fitsSystemWindows吗?在最后一种情况下,它意味着在系统栏后面绘制.
但是,如果您希望FrameLayout在状态栏后面绘制自己,则设置fitsSystemWindows为true不会起到作用,因为默认实现会执行最初记录的内容.您必须覆盖它并添加与其他提到的类相同的标志.我错过了什么吗?
ilj*_*yya 14
系统窗口是屏幕的一部分,系统绘制的是非交互式(在状态栏的情况下)或交互式(在导航栏的情况下)内容.
大多数情况下,您的应用程序不需要在状态栏或导航栏下绘制,但如果您这样做:您需要确保交互式元素(如按钮)不会隐藏在它们下面.这就是android:fitsSystemWindows ="true"属性的默认行为为您提供:它设置View的填充以确保内容不会覆盖系统窗口.
https://medium.com/google-developers/why-would-i-want-to-fitssystemwindows-4e26d9ce1eec
简而言之,如果您想弄清楚是否要使用fitsSystemWindows,可以使用Chris Banes(来自 Android 团队的开发人员)的Insetter库,它提供了一个更好的替代fitsSystemWindows. 有关更多详细信息,让我们看看下面的解释。
Android 团队在 2015 年发表了一篇很好的文章 -为什么我要 fitsSystemWindows?. 它很好地解释了属性的默认行为以及像 DrawerLayout 这样的布局如何覆盖它。
但是,那是 2015 年。早在 2017 年的 droidcon上,在 Android 上工作的Chris Banes就建议不要使用fitSystemWindows属性,除非容器文档说要使用它。这样做的原因是标志的默认行为通常不符合您的期望。这在视频中得到了很好的解释。
但是,您应该在哪些地方使用这些特殊布局fitsSystemWindows?嗯,它是DrawerLayout,CoordinatorLayout,AppBarLayout和CollapsingToolbarLayout。这些布局会覆盖默认fitsSystemWindows行为并以特殊方式对其进行处理,这在视频中再次得到了很好的解释。对属性的这种不同解释有时会导致混淆和问题,例如这里。实际上,在droidcon London 的另一个视频中,Chris Banes 承认重载默认行为的决定是一个错误(伦敦会议的 13:10 时间戳)。
好吧,如果fitSystemWindows不是最终解决方案,应该使用什么?在2019年的另一篇文章中,Chris Banes 提出了另一种解决方案,一些基于WindowInsets API 的自定义布局属性。例如,如果您希望右下角的 FAB 与导航栏相距,您可以轻松配置它:
<com.google.android.material.floatingactionbutton.FloatingActionButton
app:marginBottomSystemWindowInsets="@{true}"
app:marginRightSystemWindowInsets="@{true}"
... />
Run Code Online (Sandbox Code Playgroud)
该解决方案使用 custom @BindingAdapters,一个用于填充,另一个用于边距。这个逻辑在我上面提到的文章中有很好的描述。一些谷歌示例使用该解决方案,例如参见Owl android material app, 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
})
}
}
Run Code Online (Sandbox Code Playgroud)
如您所见,实现并非微不足道。正如我之前提到的,欢迎您使用Chris Banes 提供的Insetter库,它提供相同的功能,请参阅insetter-dbx。
另请注意,自androidx 核心库1.5.0 版以来,WindowInsets API 将发生变化。例如insets.systemWindowInsets变成insets.getInsets(Type.systemBars() or Type.ime()). 有关更多详细信息,请参阅库文档和文章。
参考:
| 归档时间: |
|
| 查看次数: |
39748 次 |
| 最近记录: |