Android的WindowInsets

1,829 阅读2分钟

背景

WindowInsets在手势导航中解决视觉冲突和手势冲突,以及挖孔屏适配上都有重要作用。

解决视觉冲突和手势冲突

先贴上Google的几篇连载文章

Android Q 手势导航背后的故事

开启全面屏体验 | 手势导航 (一)

处理视觉冲突 | 手势导航 (二)

如何处理手势冲突 | 手势导航连载 (三)

沉浸模式 | 手势导航连载 (四)

Google的该系列文章首先讲解了手势导航出现的背景,为了更加沉浸式的体验(edge-to-edge)。技术上引入了边衬区 (Insets)的概念,Insets 区域负责描述屏幕的哪些部分会与系统 UI 相交 (intersect),例如导航或状态栏。使用WindowInsets类来表示,该类包含了五种Insets,分别是:

  • 系统窗口边衬区getSystemWindowInsets()

  • 可点击区域getTappableElementInsets()

  • 系统手势边衬区getSystemGestureInsets()

  • 强制系统手势边衬区getMandatorySystemGestureInsets()

  • 稳定显示边衬区getStableInsets()

通过这些边衬区的获取来解决手势导航(或者说是实现沉浸式体验要解决的)带来的视图冲突和手势冲突

挖孔屏适配

android O(8)版本开始有挖孔屏,由于Google没有提供标准的接口查询是否是挖孔屏,所以各厂商的判断方案都不一致(令人头秃),从P(android 9)版本开始标准接口(各厂商已安标准接入),即通过WindowInsets的getDisplayCutout()方法获取挖孔信息。

可在Activity的onAttachedToWindow()的回调中去获取(因为只在view是attach的时候该WindowInsets存在)。

@Override
public void onAttachedToWindow() {
   super.onAttachedToWindow();
   // 从RootWindowInsets去获取
   WindowInsets rootWindowInsets = getWindow().getDecorView().getRootWindowInsets();
   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
      DisplayCutout displayCutout = rootWindowInsets.getDisplayCutout();
      if (displayCutout != null) {
          // 可根据屏幕旋转的情况获取对应的值
         displayCutout.getSafeInsetTop();
         displayCutout.getSafeInsetBottom();
         displayCutout.getSafeInsetLeft();
         displayCutout.getSafeInsetRight();
      }
   }
}

也可以主动触发,在回调方法中获取,适用于不一定能覆写onAttachedToWindow()的场景

private void getDisplayCutoutInfo(Activity activity) {
   if (activity == null || activity.isDestroyed() || activity.isFinishing()) {
      return;
   }

   final View decorView = activity.getWindow().getDecorView();
   // 主动触发onApplyWindowInsets回调
   decorView.requestApplyInsets();
   decorView.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
      @Override
      public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
         // 注意直接从insets中获取getDisplayCutout()会出现为null现象,导致获取不到挖孔信息
         WindowInsets rootWindowInsets = v.getRootWindowInsets();
         if (rootWindowInsets != null) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
               DisplayCutout displayCutout = rootWindowInsets.getDisplayCutout();
            }
         }
         // 设置为null可以防止多次回调;如果不设置,应该要保证onApplyWindowInsets回调里面的逻辑具有幂等性
         decorView.setOnApplyWindowInsetsListener(null);
         return insets;
      }
   });
}

附上onApplyWindowInsets的调用流程

tempsnip.png