华为刘海屏手机安卓O版本适配指导
1. 背景
刘海屏指的是手机屏幕正上方由于追求极致边框而采用的一种手机解决方案。因形似刘海儿而得名。也有一些其他叫法:挖孔屏、凹口屏等,本文档统一按照刘海屏来命名。市场上已经有越来越多的手机都支持这种屏幕形式。
谷歌在安卓P版本中已经提供了统一的适配方案,可是在安卓O版本上如何适配呢?本文将详细介绍华为安卓O版本刘海屏适配方案。使用华为提供的刘海屏SDK进行适配,此方案也会继承到华为安卓P版本手机上。在华为P版本手机中将同时支持两种方案:华为O版本方案+谷歌P版本方案。另外因为安卓O版本的刘海屏手机已经在市场上大量上市,这些手机在市场上会存续2~3年。所以建议大家现在要同时适配华为O版本方案以及谷歌P版本方案。
一季度 | 二季度 | 三季度 | 四季度 | ||
安卓O (8.1) | P20/P20 Pro/Nova 3e/荣耀10等支持刘海屏 | 已发布手机继续存在市场 | 华为刘海屏适配方案 | ||
安卓P (9.0) | 新机 | 华为刘海屏适配方案和谷歌P版本适配方案并存 |
2. 华为已经发布的刘海屏手机信息
手机 | 分辨率 | dpi | 屏幕纵横比 | 屏幕纵横比 | 刘海高度 |
nova 3e | 1080*2280 | 480 | 19:9 | 2.11111 | 90 |
荣耀 10 | 1080*2280 | 480 | 19:9 | 2.11111 | 90 |
P20 | 1080*2244 | 480 | 18.7:9 | 2.0777777 | 85 |
P20 pro | 1080*2240 | 480 | 18.7:9 | 2.074074 | 81 |
3. 华为刘海屏手机安卓O版本适配方案
设计理念:尽量减少APP的开发工作量
处理逻辑:
4. 华为刘海屏API接口
4.1 判断是否刘海屏
4.1.1 接口描述
类文件 | 接口 | 接口说明 |
com.huawei.android.util.HwNotchSizeUtil | public static boolean hasNotchInScreen() | 是否是刘海屏手机: |
4.1.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; } }
4.2 获取刘海尺寸
4.2.1 接口描述
类文件 | 接口 | 接口说明 |
com.huawei.android.util.HwNotchSizeUtil | public static int[] getNotchSize() | 获取刘海尺寸:width、height int[0]值为刘海宽度 int[1]值为刘海高度。 |
4.2.2 调用范例
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.3 应用页面设置使用刘海区显示
4.3.1 方案一
使用新增的Meta-data属性android.notch_support,在应用的AndroidManifest.xml中增加meta-data属性,此属性不仅可以针对Application生效,也可以对Activity配置生效。
1. 具体方式如下所示:
<meta-data android:name="android.notch_support" android:value="true"/>
2. 对Application生效,意味着该应用的所有页面,系统都不会做竖屏场景的特殊下移或者是横屏场景的右移特殊处理:
<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:testOnly="false" android:supportsRtl="true" android:theme="@style/AppTheme"> <meta-data android:name="android.notch_support" android:value="true"/> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity>
3. 对Activity生效,意味着可以针对单个页面进行刘海屏适配,设置了该属性的Activity系统将不会做特殊处理:
<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:testOnly="false" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <activity android:name=".LandscapeFullScreenActivity" android:screenOrientation="sensor"> </activity> <activity android:name=".FullScreenActivity"> <meta-data android:name="android.notch_support" android:value="true"/> </activity>
4.3.2 方案二
使用给window添加新增的FLAG_NOTCH_SUPPORT
1. 接口1描述:应用通过增加华为自定义的刘海屏flag,请求使用刘海区显示:
类文件 | 接口 | 接口说明 |
com.huawei.android.view.LayoutParamsEx | public void addHwFlags(int hwFlags) | 通过添加窗口FLAG的方式设置页面使用刘海区显示: public static final int FLAG_NOTCH_SUPPORT=0x00010000; |
调用范例参考:
对Application生效,意味着该应用的所有页面,系统都不会做竖屏场景的特殊下移或者是横屏场景的右移特殊处理:
/*刘海屏全屏显示FLAG*/ public static final int FLAG_NOTCH_SUPPORT=0x00010000; /** * 设置应用窗口在华为刘海屏手机使用刘海区 * @param window 应用页面window对象 */ 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"); } }
2. 接口2描述:可以通过clearHwFlags接口清除添加的华为刘海屏Flag,恢复应用不使用刘海区显示。
类文件 | 接口 | 接口说明 |
com.huawei.android.view.LayoutParamsEx | public void clearHwFlags (int hwFlags) | 通过去除窗口FLAG的方式设置页面不使用刘海区显示: public static final int FLAG_NOTCH_SUPPORT=0x00010000; |
调用范例参考:
/*刘海屏全屏显示FLAG*/ public static final int FLAG_NOTCH_SUPPORT=0x00010000; /** * 设置应用窗口在华为刘海屏手机使用刘海区 * @param window 应用页面window对象 */ public static void setNotFullScreenWindowLayoutInDisplayCutout (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("clearHwFlags", int.class); method.invoke(layoutParamsExObj, FLAG_NOTCH_SUPPORT); } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException |InstantiationException | InvocationTargetException e) { Log.e("test", "hw clear notch screen flag api error"); } catch (Exception e) { Log.e("test", "other Exception"); } }
3. 华为刘海屏flag动态添加和删除代码:
btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if(isAdd) {//add flag isAdd = false; NotchSizeUtil.setFullScreenWindowLayoutInDisplayCutout(getWindow()); getWindowManager().updateViewLayout(getWindow().getDecorView(),getWindow().getDecorView().getLayoutParams()); } else{//clear flag isAdd = true; NotchSizeUtil.setNotFullScreenWindowLayoutInDisplayCutout(getWindow()); getWindowManager().updateViewLayout(getWindow().getDecorView(),getWindow().getDecorView().getLayoutParams()); } } });
4. 使用和不使用刘海区效果对比:
设置使用刘海区效果图 不使用刘海区的效果图
设置使用刘海区效果图 不使用刘海区的效果图
4.4 获取默认和隐藏刘海区开关值接口
4.4.1 开关页面介绍
1. 老版本路径:“系统设置” > “显示” > “显示区域控制”。
2. 新版本路径:“系统设置” > “显示” > “屏幕顶部显示”
4.4.2 隐藏开关打开之后,显示规格
4.4.3 读取开关状态调用范例
public static final String DISPLAY_NOTCH_STATUS = "display_notch_status"; int mIsNotchSwitchOpen = Settings.Secure.getInt(getContentResolver(),DISPLAY_NOTCH_STATUS, 0); // 0表示“默认”,1表示“隐藏显示区域”
5. 谷歌P版本刘海屏适配方案
5.1 特性介绍
谷歌称刘海屏为凹口屏以及屏幕缺口支持, 下面的内容摘自:https://developer.android.com/preview/features#cutout
Android P 支持最新的全面屏以及为摄像头和扬声器预留空间的凹口屏幕。 通过全新的 DisplayCutout 类,可以确定非功能区域的位置和形状,这些区域不应显示内容。 要确定这些凹口屏幕区域是否存在及其位置,请使用 getDisplayCutout() 函数。
1. 全新的窗口布局属性 layoutInDisplayCutoutMode 让您的应用可以为设备凹口屏幕周围的内容进行布局。 您可以将此属性设为下列值之一:
(1)LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
(2)LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
(3)LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
2. 您可以按如下方法在任何运行 Android P 的设备或模拟器上模拟屏幕缺口:
(1)启用开发者选项。
(2)在 Developer options 屏幕中,向下滚动至 Drawing 部分并选择 Simulate a display with a cutout。
(3)选择凹口屏幕的大小。
注意:
我们建议您通过使用运行 Android P 的设备或模拟器测试凹口屏幕周围的内容显示。
5.2 接口介绍
1. 获取刘海尺寸相关接口:
https://developer.android.com/reference/android/view/DisplayCutout
所属类 | 方法 | 接口说明 |
android.view.DisplayCutout | List<Rect> getBoundingRects() | 返回Rects的列表,每个Rects都是显示屏上非功能区域的边界矩形。设备的每个短边最多只有一个非功能区域,而长边上则没有。 |
android.view.DisplayCutout | int getSafeInsetBottom() | 返回安全区域距离屏幕底部的距离,单位是px。 |
android.view.DisplayCutout | int getSafeInsetLeft () | 返回安全区域距离屏幕左边的距离,单位是px。 |
android.view.DisplayCutout | int getSafeInsetRight () | 返回安全区域距离屏幕右边的距离,单位是px。 |
android.view.DisplayCutout | int getSafeInsetTop () | 返回安全区域距离屏幕顶部的距离,单位是px。 |
2. 设置是否延伸到刘海区显示接口:
类 | 属性 | 属性说明 |
android.view.WindowManager.LayoutParams | int layoutInDisplayCutoutMode | 默认值: LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT 其他可能取值: LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER |
类 | 常量 | 常量说明 |
android.view.WindowManager.LayoutParams | int LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT | 只有当DisplayCutout完全包含在系统状态栏中时,才允许窗口延伸到DisplayCutout区域显示。 |
android.view.WindowManager.LayoutParams | int LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER | 该窗口决不允许与DisplayCutout区域重叠。 |
android.view.WindowManager.LayoutParams | int LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES | 该窗口始终允许延伸到屏幕短边上的DisplayCutout区域。 |
5.3 参考实现代码
1. 设置使用刘海区显示代码:
getSupportActionBar().hide(); getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); //设置页面全屏显示 WindowManager.LayoutParams lp = getWindow().getAttributes(); lp.layoutInDisplayCutoutMode = windowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; //设置页面延伸到刘海区显示 getWindow().setAttributes(lp);
注意:如果需要应用的布局延伸到刘海区显示,需要设置SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN。
属性:
不使用SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 使用SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
布局无法延伸到刘海显示 布局可以正真延伸到刘海区显示
2. 获取刘海屏安全显示区域和刘海尺寸信息:
contentView = getWindow().getDecorView().findViewById(android.R.id.content).getRootView(); contentView.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() { @Override public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) { DisplayCutout cutout = windowInsets.getDisplayCutout(); if (cutout == null) { Log.e(TAG, "cutout==null, is not notch screen");//通过cutout是否为null判断是否刘海屏手机 } else { List<Rect> rects = cutout.getBoundingRects(); if (rects == null || rects.size() == 0) { Log.e(TAG, "rects==null || rects.size()==0, is not notch screen"); } else { Log.e(TAG, "rect size:" + rects.size());//注意:刘海的数量可以是多个 for (Rect rect : rects) { Log.e(TAG, "cutout.getSafeInsetTop():" + cutout.getSafeInsetTop() + ", cutout.getSafeInsetBottom():" + cutout.getSafeInsetBottom() + ", cutout.getSafeInsetLeft():" + cutout.getSafeInsetLeft() + ", cutout.getSafeInsetRight():" + cutout.getSafeInsetRight() + ", cutout.rects:" + rect ); } } } return windowInsets; } });
3. 说明:
(1)通过windowInsets.getDisplayCutout()是否为null判断是否刘海屏手机,如果为null为非刘海屏:
Line 6203: 05-24 11:16:46.766 11036 11036 E Cutout_test: cutout==null, is not notch screen
(2)如果是刘海屏手机可以通过接口获取刘海信息:
Line 6211: 05-24 11:11:16.839 10733 10733 E Cutout_test: cutout.getSafeInsetTop():126, cutout.getSafeInsetBottom():0, cutout.getSafeInsetLeft():0, cutout.getSafeInsetRight():0, cutout.rects:Rect(414, 0 - 666, 126)
(3)刘海个数可以是多个:
Line 6291: 05-24 11:27:04.517 11036 11036 E Cutout_test: rect size:2 Line 6292: 05-24 11:27:04.517 11036 11036 E Cutout_test: cutout.getSafeInsetTop():84, cutout.getSafeInsetBottom():84, cutout.getSafeInsetLeft():0, cutout.getSafeInsetRight():0, cutout.rects:Rect(351, 0 - 729, 84) Line 6293: 05-24 11:27:04.517 11036 11036 E Cutout_test: cutout.getSafeInsetTop():84, cutout.getSafeInsetBottom():84, cutout.getSafeInsetLeft():0, cutout.getSafeInsetRight():0, cutout.rects:Rect(351, 1836 - 729, 1920)
6. UI适配
通过上面的2种方案(meta-data或者Flag),应用在华为刘海屏手机上就能默认使用刘海区显示了,为了避免UI被刘海区遮挡,应用还要进一步进行UI适配:
6.1 判断是否为刘海屏
判断是否为刘海屏?可以调用华为刘海屏API,参考4.1。
6.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; }
建议窗口显示内容在状态栏区域以下的另外一个原因:华为有一个自研的隐藏刘海的需求,可以参考章节4.4。
7. 调试环境
7.1 真机
购买已经上市的华为刘海屏手机,如P20/P20 Pro/荣耀10/Nova 3e等。也可以求助华为三方团队,申请华为刘海屏样机进行适配调试,需要提供样机接收人信息:公司、姓名、电话、邮编和地址,邮件反馈到:hwthirdparty@huawei.com。我们会尽快安排手机投递。
7.2 远程调试
华为终端开放实验室DevEco云测平台的远程调试已经支持华为刘海屏了。
7.2.1 新用户注册
先加入安卓绿色联盟会员,然后申请开通华为终端开放实验室DevEco云测平台帐号就可以了,所有服务都是免费的。
1. 登录 DevEco平台。
2. 使用公司邮箱进行帐号注册。
3. 将您新申请的帐号、所在公司、个人姓名及电话、负责的应用名称发送至deveco@huawei.com,申请成为安卓绿色联盟会员,通过审核后,将为您开通使用权限
7.2.2 远程调试
1. 登录 DevEco平台。
2. 选择刘海屏手机,如Nova 3e。
3. 点击“立即体验”,上传APK进行调试。
8. 技术支持
如果您在适配过程中遇到任何技术问题,可以发邮件至hwthirdparty@huawei.com,邮件主题:“华为刘海屏适配+APP名字”’
也可以加“安卓绿色联盟”微信号,加入技术支持群讨论。