[英]Right way to get insets
I have an Activity
with a RecyclerView
in a data binding layout.我在数据绑定布局中有一个带有
RecyclerView
的Activity
。 RecyclerView takes up the whole screen, and looking at making the UX go full screen, drawn under the status and nav bars. RecyclerView 占据了整个屏幕,看着让 UX 全屏显示,绘制在状态和导航栏下。
I'm calling setSystemUiVisibility
in activity's onCreate
as below.我在活动的
onCreate
调用setSystemUiVisibility
,如下所示。
window.decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
)
Now the RecyclerView
is drawn under the system bars, so I want to make sure it has enough padding so the items don't overlap with the system UI.现在
RecyclerView
绘制在系统栏下,所以我想确保它有足够的填充,以便项目不会与系统 UI 重叠。
I found 2 ways of doing this, via a BindingAdapter
.我通过
BindingAdapter
找到了两种方法。
Option 1选项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)
Option 2选项 2
var insets = view.rootWindowInsets.stableInsets
view.setPadding(0, insets.top, 0, insets.bottom)
I prefer the first, because it (with limited testing on emulators seems to) work on API 21, 28 and 29.我更喜欢第一个,因为它(似乎对模拟器的测试有限)适用于 API 21、28 和 29。
Option 2 only works on API 29, and also seems to get null on view.rootWindowInsets
if/when the view is not attached.选项 2 仅适用于 API 29,并且如果/当未附加视图时,在
view.rootWindowInsets
上似乎也为 null。 (So I guess I have to add a listener and wait for it to be attached before doing this) (所以我想我必须添加一个侦听器并在执行此操作之前等待它被附加)
So my question is, is there a down side to Option 1?所以我的问题是,选项 1 有缺点吗? Can I use it over the new API in 29?
我可以在 29 的新 API 上使用它吗? Is there any scenarios that Option 1 would not work?
有没有选项 1 不起作用的情况?
(I think Option 1 might not work well on tablets where both nav and systems bars are on the bottom, so extra padding will be applied to the wrong side.) (我认为选项 1 可能不适用于导航栏和系统栏都在底部的平板电脑,因此将在错误的一侧应用额外的填充。)
A little bit late to the party, but here is the way that I've been doing, someone might need it.聚会有点晚了,但这是我一直在做的方式,有人可能需要它。
For Android M and above, you can call View#rootWindowInsets
directly, otherwise rely on Java's Reflection to access the private field mStableInsets
对于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)
}
}
}
Update:更新:
stableInsetBottom
.etc. stableInsetBottom
等。 are now deprecated with message现在已弃用消息
Use {@link #getInsetsIgnoringVisibility(int)} with {@link Type#systemBars()} * instead.
使用 {@link #getInsetsIgnoringVisibility(int)} 和 {@link Type#systemBars()} * 代替。
Unfortunately systemBars()
was graylisted in API 29 and is blacklisted in API 30 plus using this seems to work on API 30 emulator, however (some) real devices even running API 29 throws.不幸的是,
systemBars()
在 API 29 中被列入灰名单,在 API 30 中被列入黑名单,而且使用它似乎可以在 API 30 模拟器上运行,但是(某些)甚至运行 API 29 的真实设备也会抛出异常。
Below is logcat from Galaxy S20 FE下面是来自 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)
No answer for this it seems.对此似乎没有答案。 Please put an answer if you find anything not covered below.
如果您发现以下未涵盖的内容,请提供答案。
Using Option 1 I noticed on devices that do OEM specific gesture navigation atleast, when those gesture modes are active, above will still return full navigation bar height even though no visible navigation bar is present.使用选项 1我注意到至少在执行 OEM 特定手势导航的设备上,当这些手势模式处于活动状态时,即使没有可见的导航栏,上面仍会返回完整的导航栏高度。 So above will still pad the UI when it shouldn't.
所以当它不应该时,上面仍然会填充 UI。
Option 2 keeps returning null for insets until the view is attached so if you're doing this on a BindingAdapter, it won't work.选项 2 一直为插入返回 null,直到附加视图,因此如果您在 BindingAdapter 上执行此操作,它将不起作用。 It needs to be called after the view is attached.
需要在附加视图后调用。
My current solution is as below.我目前的解决方案如下。
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 ¯\_(ツ)_/¯
}
Caveats注意事项
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.