[英]Right way to get insets
我在数据绑定布局中有一个带有RecyclerView
的Activity
。 RecyclerView 占据了整个屏幕,看着让 UX 全屏显示,绘制在状态和导航栏下。
我在活动的onCreate
调用setSystemUiVisibility
,如下所示。
window.decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
)
现在RecyclerView
绘制在系统栏下,所以我想确保它有足够的填充,以便项目不会与系统 UI 重叠。
我通过BindingAdapter
找到了两种方法。
选项1
var statusBar = 0
var resourceId = view.resources.getIdentifier("status_bar_height", "dimen", "android")
if (resourceId > 0) {
statusBar = view.resources.getDimensionPixelSize(resourceId)
}
var navBar = 0
resourceId = view.resources.getIdentifier("navigation_bar_height", "dimen", "android")
if (resourceId > 0) {
navBar = view.resources.getDimensionPixelSize(resourceId)
}
view.setPadding(0, statusBar, 0, navBar)
选项 2
var insets = view.rootWindowInsets.stableInsets
view.setPadding(0, insets.top, 0, insets.bottom)
我更喜欢第一个,因为它(似乎对模拟器的测试有限)适用于 API 21、28 和 29。
选项 2 仅适用于 API 29,并且如果/当未附加视图时,在view.rootWindowInsets
上似乎也为 null。 (所以我想我必须添加一个侦听器并在执行此操作之前等待它被附加)
所以我的问题是,选项 1 有缺点吗? 我可以在 29 的新 API 上使用它吗? 有没有选项 1 不起作用的情况?
(我认为选项 1 可能不适用于导航栏和系统栏都在底部的平板电脑,因此将在错误的一侧应用额外的填充。)
聚会有点晚了,但这是我一直在做的方式,有人可能需要它。
对于Android M及以上,可以直接调用View#rootWindowInsets
,否则依赖Java的Reflection访问私有字段mStableInsets
fun getStableInsets(view: View): Rect {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val windowInsets = view.rootWindowInsets
if (windowInsets != null) {
Rect(windowInsets.stableInsetLeft, windowInsets.stableInsetTop,
windowInsets.stableInsetRight, windowInsets.stableInsetBottom)
} else {
// TODO: Edge case, you might want to return a default value here
Rect(defaultInsetLeft, defaultInsetTop, defaultInsetRight, defaultInsetBottom)
}
} else {
val attachInfoField = View::class.java.getDeclaredField("mAttachInfo")
attachInfoField.isAccessible = true
val attachInfo = attachInfoField.get(view);
if (attachInfo != null) {
val stableInsetsField = attachInfo.javaClass.getDeclaredField("mStableInsets")
stableInsetsField.isAccessible = true
Rect(stableInsetsField.get(attachInfo) as Rect)
} else {
// TODO: Edge case, you might want to return a default value here
Rect(defaultInsetLeft, defaultInsetTop, defaultInsetRight, defaultInsetBottom)
}
}
}
更新:
stableInsetBottom
等。 现在已弃用消息
使用 {@link #getInsetsIgnoringVisibility(int)} 和 {@link Type#systemBars()} * 代替。
不幸的是, systemBars()
在 API 29 中被列入灰名单,在 API 30 中被列入黑名单,而且使用它似乎可以在 API 30 模拟器上运行,但是(某些)甚至运行 API 29 的真实设备也会抛出异常。
下面是来自 Galaxy S20 FE 的 logcat
Accessing hidden method Landroid/view/WindowInsets$Type;->systemBars()I (blacklist, linking, denied)
2021-01-17 01:45:18.348 23013-23013/? E/AndroidRuntime: FATAL EXCEPTION: main
Process: test.app.package, PID: 23013
java.lang.NoSuchMethodError: No static method systemBars()I in class Landroid/view/WindowInsets$Type; or its super classes (declaration of 'android.view.WindowInsets$Type' appears in /system/framework/framework.jar!classes3.dex)
对此似乎没有答案。 如果您发现以下未涵盖的内容,请提供答案。
使用选项 1我注意到至少在执行 OEM 特定手势导航的设备上,当这些手势模式处于活动状态时,即使没有可见的导航栏,上面仍会返回完整的导航栏高度。 所以当它不应该时,上面仍然会填充 UI。
选项 2 一直为插入返回 null,直到附加视图,因此如果您在 BindingAdapter 上执行此操作,它将不起作用。 需要在附加视图后调用。
我目前的解决方案如下。
if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
view.doOnAttach {
var bottom = it.rootWindowInsets?.stableInsetBottom?: 0
var top = it.rootWindowInsets?.stableInsetTop?: 0
view.setPadding(0, top, 0, bottom)
}
}
else {
// use option1, old devices don't have custom OEM specific gesture navigation.
// or.. just don't support versions below Android M ¯\_(ツ)_/¯
}
注意事项
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.