适配刘海屏的两种情况
  1.沉浸式状态栏:适配方案就是将窗口布局下移,预留出状态栏的空间。
  2.全屏显示模式:不做适配的话状态栏会呈现一条黑边。适配方案是首先判断系统版本,是Android P及以上就按照官方的API来适配,否则根据手机厂商的适配方案进行适配
 
 

1、 针对沉浸式状态栏3种方案适配--可以成功的避开状态栏(危险区域)
     //方法一:利用fitsSystemWindows属性
            当我们给最外层View设置了android:fitsSystemWindows="true"属性后,当设置了透明状态栏或者透明导航栏后,就会自动给View添加paddingTop或paddingBottom属性,
            这样就在屏幕上预留出了状态栏的高度,我们的布局就不会占用状态栏来显示了。
            <?xml version="1.0" encoding="utf-8"?>
             <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                 android:id="@+id/ll_root"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
                 android:background="@mipmap/bg"
                 android:fitsSystemWindows="true"
                 android:orientation="vertical">
                 <Button
                     android:layout_width="150dp"
                     android:layout_height="wrap_content"
                     android:layout_gravity="center_horizontal" />
             </LinearLayout>
             
     //方法二:根据状态栏高度手动设置paddingTop
             这种方法的实现本质上和设置fitsSystemWindows是一样的,首先获取状态栏高度,然后设置根布局的paddingTop等于状态栏高度就可以
              public class ImmersiveActivity extends AppCompatActivity {
                 @Override
                 protected void onCreate(@Nullable Bundle savedInstanceState) {
                     super.onCreate(savedInstanceState);
                     setContentView(R.layout.activity_immersive);
                     // 透明状态栏
                     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                         getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
                     }
                     LinearLayout llRoot = findViewById(R.id.ll_root);
                     // 设置根布局的paddingTop
                     llRoot.setPadding(0, getStatusBarHeight(this), 0, 0);
                 }                /**
                  * 获取状态栏高度          
                  */
                 public int getStatusBarHeight(Context context) {
                     int statusBarHeight = 0;
                     int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
                     if (resourceId > 0) {
                         statusBarHeight = context.getResources().getDimensionPixelSize(resourceId);
                     }
                     return statusBarHeight;
                 }
              }  
  
     //方法三:在布局中添加一个和状态栏高度相同的View
              这里在根布局中添加了一个透明的View,高度和状态栏高度相同。这种方法的好处是可以自定义填充状态栏View的背景,更灵活地实现我们想要的效果
             public class ImmersiveActivity extends AppCompatActivity {
                 protected void onCreate(@Nullable Bundle savedInstanceState) {
                     super.onCreate(savedInstanceState);
                     setContentView(R.layout.activity_immersive);
                     // 透明状态栏
                     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                         getWindow().addFlags(
                                 WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
                     }
                     LinearLayout llRoot = findViewById(R.id.ll_root);
                     View statusBarView = new View(this);
                     statusBarView.setBackgroundColor(Color.TRANSPARENT);
                     ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(
                             ViewGroup.LayoutParams.MATCH_PARENT,
                             getStatusBarHeight(this));
                     // 在根布局中添加一个状态栏高度的View
                     llRoot.addView(statusBarView, 0, lp);
                 }                /**
                  * 获取状态栏高度
                  */
                 public int getStatusBarHeight(Context context) {
                     int statusBarHeight = 0;
                     int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
                     if (resourceId > 0) {
                         statusBarHeight = context.getResources().getDimensionPixelSize(resourceId);
                     }
                     return statusBarHeight;
                 }
             }
     
     
 2、针对全屏显示的适配
     2.1、Android P及以上
         谷歌官方从Android P开始给开发者提供了刘海屏相关的API,通过DisplayCutout类可以获得安全区域的范围以及刘海区域的信息,需要注意只有API Level在28及以上才可以调用。
     /**
      * 获得刘海区域信息
     */
         @TargetApi(28)
         public void getNotchParams() {
             final View decorView = getWindow().getDecorView();
             if (decorView != null) {
                 decorView.post(new Runnable() {
                     @Override
                     public void run() {
                         WindowInsets windowInsets = decorView.getRootWindowInsets();
                         if (windowInsets != null) {
                             // 当全屏顶部显示黑边时,getDisplayCutout()返回为null
                             DisplayCutout displayCutout = windowInsets.getDisplayCutout();
                             Log.e("TAG", "安全区域距离屏幕左边的距离 SafeInsetLeft:" + displayCutout.getSafeInsetLeft());
                             Log.e("TAG", "安全区域距离屏幕右部的距离 SafeInsetRight:" + displayCutout.getSafeInsetRight());
                             Log.e("TAG", "安全区域距离屏幕顶部的距离 SafeInsetTop:" + displayCutout.getSafeInsetTop());
                             Log.e("TAG", "安全区域距离屏幕底部的距离 SafeInsetBottom:" + displayCutout.getSafeInsetBottom());
                             // 获得刘海区域
                             List<Rect> rects = displayCutout.getBoundingRects();
                             if (rects == null || rects.size() == 0) {
                                 Log.e("TAG", "不是刘海屏");
                             } else {
                                 Log.e("TAG", "刘海屏数量:" + rects.size());
                                 for (Rect rect : rects) {
                                     Log.e("TAG", "刘海屏区域:" + rect);
                                 }
                             }
                         }
                     }
                 });
             }
         }
     如果是在style中设置了全屏模式,在适配之前,顶部状态栏区域显示一条黑边,在适配之前无法通过DisplayCutout判断是否存在刘海屏。
     因此只能对于所有设备都添加适配代码。Android P中增加了一个窗口布局参数属性layoutInDisplayCutoutMode,该属性有三个值可以取:
     1、LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT:默认的布局模式,如果没有设置为全屏显示模式,就允许窗口延伸到刘海区域,否则不允许。
     2、LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER:永远不允许窗口延伸到刘海区域。
     3、LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES:始终允许窗口延伸到屏幕短边上的刘海区域
     
             public class FullScreenActivity extends AppCompatActivity {
             protected void onCreate(@Nullable Bundle savedInstanceState) {
                 super.onCreate(savedInstanceState);
                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
                     WindowManager.LayoutParams lp = getWindow().getAttributes();
                     //1、 仅当缺口区域完全包含在状态栏之中时,才允许窗口延伸到刘海区域显示
                     lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
                     //2、 永远不允许窗口延伸到刘海区域
                     lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
                     //3、 始终允许窗口延伸到屏幕短边上的刘海区域
                     lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
                     getWindow().setAttributes(lp);
                 }
             }
         }
     
     由结果可以看出第一种和第二种都会有一条黑边,也就是不允许窗口布局延伸到刘海区域,第三种允许窗口布局延伸到刘海区域。因此采用第三种模式。
     
     
     
    2.2、Android P以下
          针对Android P以下的手机,主要还是针对目前主流的手机品牌,总结了华为、小米、Vivo和Oppo的适配方案。
          
     华为适配方案:
         1、判断是否有刘海屏
          /**
          * @return true:有刘海屏;false:没有刘海屏
          */
         public static boolean hasNotch(Context context) {
             boolean ret = false;
             try {
                 ClassLoader cl = context.getClassLoader();
                 Class HwNotchSizeUtil = cl.loadClass("com.huawei.android.util.HwNotchSizeUtil");
                 Method get = HwNotchSizeUtil.getMethod("hasNotchInScreen");
                 ret = (boolean) get.invoke(HwNotchSizeUtil);
             } catch (Exception e) {
                 Log.e("test", "hasNotchInScreen Exception");
             } finally {
                 return ret;
             }
         }       
         
         2、设置使用刘海区显示
            两种适配方案:
            方案一:使用新增的meta-data属性android.notch_support,在应用的AndroidManifest.xml中增加或者在Activity中增加,此属性不仅可以针对Application生效,也可以对Activity配置生效
                    <meta-data android:name="android.notch_support" android:value="true"/>
     
            方案二:使用给window添加新增的FLAG_NOTCH_SUPPORT
                     public static void setFullScreenWindowLayoutInDisplayCutout(Window window) {
                         if (window == null) {
                             return;
                         }
                         WindowManager.LayoutParams layoutParams = window.getAttributes();
                         try {
                             Class layoutParamsExCls = Class.forName("com.huawei.android.view.LayoutParamsEx");
                             Constructor con = layoutParamsExCls.getConstructor(WindowManager.LayoutParams.class);
                             Object layoutParamsExObj = con.newInstance(layoutParams);
                             Method method = layoutParamsExCls.getMethod("addHwFlags", int.class);
                             method.invoke(layoutParamsExObj, FLAG_NOTCH_SUPPORT);                 
                         } catch (Exception e) {
                             Log.e("test", "other Exception");
                         }
                     }
     
     
     小米适配方案
         1、判断是否有刘海屏
             /**
              * @return true:有刘海屏;false:没有刘海屏
              */
             public static boolean hasNotch(Context context) {
                 boolean ret = false;
                 try {
                     ClassLoader cl = context.getClassLoader();
                     Class SystemProperties = cl.loadClass("android.os.SystemProperties");
                     Method get = SystemProperties.getMethod("getInt", String.class, int.class);
                     ret = (Integer) get.invoke(SystemProperties, "ro.miui.notch", 0) == 1;
                 } catch (Exception e) {
                     e.printStackTrace();
                 } finally {
                     return ret;
                 }
             }
         2、设置使用刘海区显示
            两种适配方案:
            方案一:Application级别的控制接口,在 Application 下增加一个 meta-data,用以声明该应用窗口是否可以延伸到状态栏
                   <meta-data android:name="notch.config" android:value="portrait|landscape"/>
        
            方案二:Window级别的控制接口,通过给Window添加Flag也可以实现将窗口布局延伸到状态栏中显示。
                   /*刘海屏全屏显示FLAG*/
                     public static final int FLAG_NOTCH_SUPPORT = 0x00000100; // 开启配置
                     public static final int FLAG_NOTCH_PORTRAIT = 0x00000200; // 竖屏配置
                     public static final int FLAG_NOTCH_HORIZONTAL = 0x00000400; // 横屏配置                    /**
                      * 设置应用窗口在刘海屏手机使用刘海区              
                      */
                     public static void setFullScreenWindowLayoutInDisplayCutout(Window window) {
                         // 竖屏绘制到耳朵区
                         int flag = FLAG_NOTCH_SUPPORT | FLAG_NOTCH_PORTRAIT;
                         try {
                             Method method = Window.class.getMethod("addExtraFlags",int.class);
                             method.invoke(window, flag);
                         } catch (Exception e) {
                             Log.e("test", "addExtraFlags not found.");
                         }
                     }
                         
     
     Vivo、Oppo适配方案
         把Vivo和Oppo放在一起说,官方提供的资料不像华为和小米那么详细,只是提供了判断是否有刘海屏的方法
         1、vivo判断是否有刘海屏
             public static final int VIVO_NOTCH = 0x00000020; // 是否有刘海
             public static final int VIVO_FILLET = 0x00000008; // 是否有圆角
             
             public static boolean hasNotch(Context context) {
                 boolean ret = false;
                 try {
                     ClassLoader classLoader = context.getClassLoader();
                     Class FtFeature = classLoader.loadClass("android.util.FtFeature");
                     Method method = FtFeature.getMethod("isFeatureSupport", int.class);
                     ret = (boolean) method.invoke(FtFeature, VIVO_NOTCH);            
                 } catch (Exception e) {
                     Log.e("Notch", "hasNotchAtVivo Exception");
                 } finally {
                     return ret;
                 }
             }
     
              OPPO判断是否有刘海屏
              public static boolean hasNotch(Context context) {
                  return context.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism");
              }