簡體   English   中英

獲取插入的正確方法

[英]Right way to get insets

我在數據綁定布局中有一個帶有RecyclerViewActivity 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 ¯\_(ツ)_/¯
}

注意事項

  • 當導航模式改變時,一些 OEM(至少是 OnePlus)決定不重啟某些活動,尤其是暫停的活動。 因此,如果用戶決定離開您的應用程序,更改導航模式並返回,您的應用程序可能仍會與導航欄重疊,直到 Activity 重新啟動。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM