获取插图的正确方法

问题描述 投票:0回答:3

我在数据绑定布局中有一个

Activity
RecyclerView
。 RecyclerView 占据整个屏幕,并希望使 UX 全屏显示,绘制在状态栏和导航栏下方。

我在活动的

setSystemUiVisibility
中调用
onCreate
,如下所示。

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 android-layout
3个回答
2
投票

聚会有点晚了,但这是我一直在做的方式,有人可能需要它。

对于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)
      }
   }     
}

0
投票

更新:

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 ¯\_(ツ)_/¯ }

注意事项

    一些 OEM(至少是 OnePlus)决定在导航模式更改时不重新启动某些活动,尤其是暂停的活动。因此,如果用户决定离开您的应用程序、更改导航模式并返回,您的应用程序可能仍会与导航栏重叠,直到 Activity 重新启动。

0
投票
对于API级别30以上,我们可以使用以下方法来获取稳定的插图,

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { binding.root.apply { doOnAttach { val inset = it.rootWindowInsets?.getInsets(WindowInsets.Type.systemBars()) println("inset: left: ${inset?.left} right: ${inset?.right} top:${inset?.top} bottom: ${inset?.bottom}") } } }
    
© www.soinside.com 2019 - 2024. All rights reserved.