简体   繁体   English

获取插入的正确方法

[英]Right way to get insets

I have an Activity with a RecyclerView in a data binding layout.我在数据绑定布局中有一个带有RecyclerViewActivity 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注意事项

  • Some OEMs (well atleast OnePlus) decided not to restart some activities, especially ones that are paused, when the navigation mode changed.当导航模式改变时,一些 OEM(至少是 OnePlus)决定不重启某些活动,尤其是暂停的活动。 So if the user decides to switch away from your app, change the navigation mode and return, your app may still overlap navigation bar until the activity is restarted.因此,如果用户决定离开您的应用程序,更改导航模式并返回,您的应用程序可能仍会与导航栏重叠,直到 Activity 重新启动。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 有没有办法在调度后获取现有的窗口插图? - Is there a way to get existing window insets after dispatch? 如何获取 android 应用程序的状态栏插图? - How to get statusbar insets for android app? 什么是获得经过时间并重置它的正确方法 - Whats is the right way to get elapsed time and reset it 如何以正确的方式从 EditText 获得双倍? - How to get double from EditText the right way? 这是在棒棒糖前设备上产生涟漪效应的正确方法吗? - Is this the right way to get the ripple effect on pre-lollipop devices? 在 Android 中的 retrofit GET 调用中传递参数的正确方法是什么? - What is the right way to pass the parameter in the retrofit GET call in Android? null 在片段中捆绑,在片段中获取捆绑的正确方法是什么? - null bundle in fragment , what is the right way to get a bundle in fragment? 从外部存储中删除图像后,是否有办法立即获取图像的uri? - Is there a way to get the uri of an image right after it is removed from external storage? 什么是让Android Wear刷新媒体通知的正确方法? - What's the right way to get Android Wear to refresh a media notification? getPreferredPreviewSizeForVideo()是获得正确预览大小的好方法吗? - Is getPreferredPreviewSizeForVideo() a good way go get the right preview size?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM