刘海屏适配方略

适配刘海屏的两种情况
  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");
             }
    
   

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值