随着 Apple 发布 iPhone X 以后,各大手机厂商也开始模仿这种刘海屏的设计,并且刘海屏手机的用户也是愈来愈大,前段时间将项目进行了全部主流厂商的刘海屏手机的适配,以便让刘海屏手机的用户也能有更好的体验。html
刘海屏手机由于比日常的手机多了一块顶部的遮挡性刘海,因此会形成顶部 Toolbar 以及搜索框的遮挡,并且有些厂商的手机(vivo、华为),默认是在「无状态栏」的界面将状态栏进行黑化显示,这时候会致使系统下移,从而致使底部的一些 UI 被截断。除此以外,一些控件的显示规则还会受到影响,如 PopupWindow 的显示高度会在「无状态栏」的界面中比普通手机低一个「刘海的高度」,从而遮挡住原先在 PopupWindow 周围的图标。android
理论上来说,经过 Android P 版本提供的刘海屏相关接口,判断手机是否为刘海屏手机,以及进行一些相应的处理是最合适的方式,但如今在国内使用 Android P 的接口是不现实的,因此只能经过各大厂商提供的技术文档来进行适配,但适配的流程基本是一致的。c#
其中须要着重处理的是:api
如今国内的主流机型(华为、vivo、OPPO、小米)在刘海屏的显示上分为两个阵营:bash
因此,咱们在进行刘海屏适配的时候,首先须要经过一些手段,统一各大厂商的显示方案,让全部的刘海屏手机都利用状态栏的界面,「告知系统」咱们已经适配了刘海屏,确保系统不会下移咱们的应用,保留原生体验。markdown
这里主要有两种方式:oop
一、设置屏幕高宽比例spa
由于刘海屏手机的「宽高比」比以前的手机大,若是不适配的话,Android 默认为最大的宽高比为 1.86, 小于刘海屏手机的宽高比,所以咱们须要申明更高的宽高比来告诉系统,咱们应用已经适配了刘海屏。设计
只要在 AndroidManifest.xml 中加入以下配置:code
<meta-data android:name="android.max_aspect" android:value="2.1"/> 复制代码
也能够在 Application 添加属性:
android:maxAspectRatio="ratio_float" 复制代码
ps:这个属性须要 API 26 才支持
二、设置应用支持 resize
咱们还能够经过设置应用支持 resizeable,来告诉系统咱们适配了刘海屏,并且这也是 Google 官方推荐的方式。不过须要注意的是,使用这个属性以后,应用也会跟着支持分屏模式。只须要在 AndroidManifest.xml 中添加:
android:resizeableActivity="true" 复制代码
对于刘海屏适配,咱们将界面分为两种:
所以,咱们进行刘海屏适配,其实针对的就是没有状态栏的界面,而有状态栏的界面显示是正常的。对于没有状态栏的界面,主要是将对被刘海遮挡到的控件,设置对应刘海高度的 MarginTop,从而避免控件被遮挡。而对于底部可能被截断的界面,能够考虑将底部作成 ScrollView 的形式。
如今 Android P 的接口还无法用,但各手机厂商都制定了本身的 API,对此咱们须要对各大机型进行特殊的适配,这里主要介绍 vivo、OPPO、华为 这三种主流手机的适配方案。
华为做为国内的手机厂商大头,本身仿照 Android P 提供的 API,实现了一套几乎差很少的 API,因此咱们若是想要告诉系统咱们的应用适配了刘海屏,最好直接使用华为的 API,这样才是最保险的。
如下代码来自:华为刘海屏适配官方技术指导
① 方案一:在 AndroidManifest.xml 中增长 meta-data 属性,此属性不只能够针对 Application 生效,也能够对 Activity 配置生效:
<meta-data android:name="android.notch_support" android:value="true"/> 复制代码
增长这个属性以后,系统就不会对应用进行下移处理,从而保证原生体验。
② 方案二:经过添加窗口 FLAG 的方式设置界面使用刘海区:
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(LayoutParams.class); Object layoutParamsExObj = con.newInstance(layoutParams); Method method = layoutParamsExCls.getMethod("addHwFlags", int.class); method.invoke(layoutParamsExObj, FLAG_NOTCH_SUPPORT); } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException |InstantiationException | InvocationTargetException e) { Log.e("test", "hw add notch screen flag api error"); } catch (Exception e) { Log.e("test", "other Exception"); } } 复制代码
public static boolean hasNotchInHuawei(Context context) { boolean hasNotch = false; try { ClassLoader cl = context.getClassLoader(); Class HwNotchSizeUtil = cl.loadClass("com.huawei.android.util.HwNotchSizeUtil"); Method hasNotchInScreen = HwNotchSizeUtil.getMethod("hasNotchInScreen"); if(hasNotchInScreen != null) { hasNotch = (boolean) hasNotchInScreen.invoke(HwNotchSizeUtil); } } catch (Exception e) { e.printStackTrace(); } return hasNotch; } 复制代码
public static int[] getNotchSize(Context context) { int[] ret = new int[]{0, 0}; try { ClassLoader cl = context.getClassLoader(); Class HwNotchSizeUtil = cl.loadClass("com.huawei.android.util.HwNotchSizeUtil"); Method get = HwNotchSizeUtil.getMethod("getNotchSize"); ret = (int[]) get.invoke(HwNotchSizeUtil); } catch (ClassNotFoundException e) { Log.e("test", "getNotchSize ClassNotFoundException"); } catch (NoSuchMethodException e) { Log.e("test", "getNotchSize NoSuchMethodException"); } catch (Exception e) { Log.e("test", "getNotchSize Exception"); } finally { return ret; } 复制代码
OPPO 是主流厂商中的一股清流,学 iPhoneX 是最像的,OPPO 手机对于不显示状态栏的界面,采起的是「状态栏原先的位置也用于显示界面」的方案,因此咱们只要进行相关控件的位置移动就能够了。
如下代码来自: OPPO 凹形屏适配说明
public static boolean hasNotchInOppo(Context context) { return context.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism"); } 复制代码
对于 OPPO 刘海屏手机的刘海高度,OPPO 官方的文档没有提供相关的 API,但官方文档表示 OPPO 手机的刘海高度和状态栏的高度是一致的,并且我也对此进行了验证,确实如此。因此咱们能够直接获取状态栏的高度,做为 OPPO 手机的刘海高度。
public static 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 ; } 复制代码
vivo 提供的技术文档对于开发者来讲是最不友好的,只提供了一个 API 来进行刘海屏的判断,并无提供刘海高度的获取方式,咱们只能经过获取状态栏高度来当作刘海的高度,但在某些机型可能会有些误差。
官方文档:vivo 手机适配指南
public static boolean hasNotchInVivo(Context context) { boolean hasNotch = false; try { ClassLoader cl = context.getClassLoader(); Class ftFeature = cl.loadClass("android.util.FtFeature"); Method[] methods = ftFeature.getDeclaredMethods(); if (methods != null) { for (int i = 0; i < methods.length; i++) { Method method = methods[i]; if(method != null) { if (method.getName().equalsIgnoreCase("isFeatureSupport")) { hasNotch = (boolean) method.invoke(ftFeature, 0x00000020); break; } } } } } catch (Exception e) { e.printStackTrace(); hasNotch = false; } return hasNotch; } 复制代码
以上即是在以前在进行 Android 刘海屏适配的时候,所积累的一些经验和心得。将其记录下来,以便本身之后进行回顾,同时也但愿这篇文章能对进行刘海屏适配的同窗一些帮助。