背景
自从iphone x发布后,各大厂商也发布了类似的刘海屏手机(“顶部屏幕凹槽设计”),开发者应该如何适配呢?
原理
为什么会有刘海屏?
因为大家有自拍的需求,需要摄像头前置,除了摄像头前置外,刘海屏上还有一些其他的传感器,所以不同厂商的刘海屏长度也不相同。
这里主要是介绍一下Android P发布之后刘海屏的适配以及Android P之前的适配。为什么要分开呢?因为Android P之前官方还没提供API来进行适配,都是由各家厂商来提供适配方案的。
2.6 那么刘海屏该如何适配呢?
2.6.1 如果页面存在状态栏
[if !supportLists]· [endif]那么很简单,不用适配,因为刘海区域会包含在状态栏中了。
[if !supportLists]· [endif]如果不想看到刘海区域,可以使用LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER将刘海区域变成一条黑色边。
2.6.2 如果页面是全屏显示
[if !supportLists]· [endif]不适配的话将会留出一条黑色边。
[if !supportLists]· [endif]要做到真正全屏的话,那么就先要获取到刘海的区域(危险区域),内容部分(操作按钮等)应当避开危险区域,保证在安全区域中展示。横屏的话两边都需要注意避开刘海(危险区域)。
华为适配刘海屏主要有以下几个步骤: 1.配置meta-data
[if !supportLists]2. [endif]检测是否存在刘海屏3.获取刘海屏的参数4. UI适配
1.配置meta-data 华为新增的Meta-data属性android.notch_support在应用的AndroidManifest.xml中增加meta-data属性,此属性不仅可以针对Application生效,也可以对Activity配置生效,具体方式如下所示:
[if !supportLists]· [endif]1
①对Application生效,意味着该应用的所有页面,(会对你App的所有页面都进行适配)系统都不会做竖屏场景的特殊下移或者是横屏场景的右移特殊处理:
② 对Activity生效,意味着可以针对单个页面进行刘海屏适配,设置了该属性的Activity系统将不会做特殊处理:
2.检测是否存在刘海屏
public static boolean hasNotchInScreen(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 (ClassNotFoundException e) {
Log.e("test", "hasNotchInScreen ClassNotFoundException");
} catch (NoSuchMethodException e) {
Log.e("test", "hasNotchInScreen NoSuchMethodException");
} catch (Exception e) {
Log.e("test", "hasNotchInScreen Exception");
} finally {
return ret;
}
}
3.获取刘海屏的参数
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;
}
}
4. UI适配 通过增加上面适配方案提到的配置(meta-data或者是Flag),应用在华为刘海屏手机上就能够默认使用刘海区显示了,但是为了避免出现UI被刘海区遮挡的问题,还是需要应用自己做一些额外的UI适配工作:(1)判断是否刘海屏,通过华为刘海屏SDK的API判断,具体参考3.2.1章节(2)如果是刘海屏手机需要应用自己调整布局避开刘海区,布局原则:保证重要的文字、图片和视频信息、可点击的控件和图标还有应用弹窗等等布局建议显示在状态栏区域以下(安全区域);不重要,遮挡不会出现问题的布局可以延伸到状态栏区域(危险区域)显示,按照这种布局原则修改,可以一次修改就能适配所有的刘海屏手机:
获取系统状态栏高度接口:
public static int getStatusBarHeight(Context context) {
int result = 0;
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = context.getResources().getDimensionPixelSize(resourceId);
}
return result;
}
vivo & OPPO
vivo 和 OPPO官网仅仅给出了适配指导,没有给出具体方案,简单总结为: 如有是具有刘海屏的手机,竖屏显示状态栏,横屏不要在危险区显示重要信息或者设置点击事件。
首先,判断是不是刘海屏手机。OPPO判断方法:
public static boolean hasNotchInOppo(Context context){
return context.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism");
}
vivo的判断方法:
public static final int NOTCH_IN_SCREEN_VOIO=0x00000020;//是否有凹槽public static final int ROUNDED_IN_SCREEN_VOIO=0x00000008;//是否有圆角public static boolean hasNotchInScreenAtVoio(Context context){
boolean ret = false;
try {
ClassLoader cl = context.getClassLoader();
Class FtFeature = cl.loadClass("com.util.FtFeature");
Method get = FtFeature.getMethod("isFeatureSupport",int.class);
ret = (boolean) get.invoke(FtFeature,NOTCH_IN_SCREEN_VOIO);
} catch (ClassNotFoundException e)
{ Log.e("test", "hasNotchInScreen ClassNotFoundException"); }
catch (NoSuchMethodException e)
{ Log.e("test", "hasNotchInScreen NoSuchMethodException"); }
catch (Exception e)
{ Log.e("test", "hasNotchInScreen Exception"); }
finally
{ return ret; }
}
然后在进行适配,官方这方面的资料很少也不是很详细
google官方
google从Android P开始为刘海屏提供支持,目前提供了一个类和三种模式: 一个类指的是可以用DisplayCutout这个类找出刘海(cutout)的位置和形状,调用getDisplayCutout()这个方法可以获取刘海(cut
out)的位置和区域。
3.1 开启刘海屏
我们在全屏的页面,需要单独开启支持刘海屏。而Google 提供的适配方案,可以设置是否在全屏模式下,使用刘海屏的区域。
WindowManager.LayoutParams lp
=getWindow().getAttributes();
lp.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
getWindow().setAttributes(lp);
新的布局属性layoutInDisplayCutoutMode 包含三种可选的模式,
模式模式说明
LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT只有当DisplayCutout完全包含在系统栏中时,才允许窗口延伸到DisplayCutout区域。 否则,窗口布局不与DisplayCutout区域重叠。
LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER该窗口决不允许与DisplayCutout区域重叠。
LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES该窗口始终允许延伸到屏幕短边上的DisplayCutout区域。
[if !supportLists]1. [endif]LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES模式会让屏幕到延申刘海区域中。
[if !supportLists]2. [endif]LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER模式不会让屏幕到延申刘海区域中,会留出一片黑色区域。
[if !supportLists]3. [endif]LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT模式在全屏显示下跟LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER一样。
这里设置为全屏的显示效果,三种模式的结果如下图所示
[if !supportLists]1. [endif]LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES模式会让屏幕到延申刘海区域中。
[if !supportLists]2. [endif]LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER模式不会让屏幕到延申刘海区域中,会留出一片黑色区域。
[if !supportLists]3. [endif]LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT模式在全屏显示下跟LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER一样。
LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT模式在沉浸式状态栏下的效果:
3.2 刘海屏的高度
在全屏模式下,我们需要有办法获取到刘海屏凹槽的高度,才可以做到设计和布局的时候,留出安全距离。
虽然Google 要求,刘海屏的凹槽,必须和刘海的高度保持一致,而刘海屏又被隐藏在状态栏了,所以有一个思路是直接获取状态栏的高度,来判断刘海之外,可布局的安全区域。
不过Android P 已经预留出了标准的测量 刘海屏凹槽 的 Api:DisplayCutout。
刘海屏的凹槽,就在屏幕的中间,所以只有getSafeInsetTop() 方法返回的结果
: