背景

刘海屏指的是手机屏幕正上方由于追求极致边框而采用的一种手机解决方案。因形似刘海儿而得名。也有一些其他叫法:挖孔屏、凹口屏等,本文档统一按照刘海屏来命名。市场上已经有越来越多的手机都支持这种屏幕形式。

谷歌在安卓P版本中已经提供了统一的适配方案,可是在安卓O版本上如何适配呢?本文将详细介绍华为安卓O版本刘海屏适配方案。使用华为提供的刘海屏SDK进行适配,此方案也会继承到华为安卓P版本手机上。在华为P版本手机中将同时支持两种方案:华为O版本方案+谷歌P版本方案。另外因为安卓O版本的刘海屏手机已经在市场上大量上市,这些手机在市场上会存续2~3年。所以建议大家现在要同时适配华为O版本方案以及谷歌P版本方案。

华为刘海屏设计流程

设计理念:尽量减少APP的开发工作量

处理逻辑:

从流程图可以看出,华为的手机刘海屏的处理流程中,只要我们应用是全屏显示或者横屏显示,只要我们未做适配,系统默认就是页面下移,所以我们只需要适配全屏显示和横屏显示的页面,其它页面,还是按照照常即可。

华为刘海屏API接口

##1. 判断是否刘海屏 ##

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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;

}

}

2. 获取刘海尺寸

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
*int[0]值为刘海宽度 int[1]值为刘海高度
*/
public static int[] getNotchSize(Context context) {

int[] ret = new int[]{0, 0};//int[0]值为刘海宽度 int[1]值为刘海高度

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;

}

}

应用页面设置使用刘海区显示

1.方案一

使用新增的Meta-data属性android.notch_support

在应用的AndroidManifest.xml中增加meta-data属性,此属性不仅可以针对Application生效,也可以对Activity配置生效。

具体方式如下所示:

1
<meta-data android:name="android.notch_support" android:value="true"/>

对Application生效,意味着该应用的所有页面,系统都不会做竖屏场景的特殊下移或者是横屏场景的右移特殊处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<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>

对Activity生效,意味着可以针对单个页面进行刘海屏适配,设置了该属性的Activity系统将不会做特殊处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<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>

2.方案二

使用给window添加新增的FLAG_NOTCH_SUPPORT
对Application生效,意味着该应用的所有页面,系统都不会做竖屏场景的特殊下移或者是横屏场景的右移特殊处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*刘海屏全屏显示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");
}
}

可以通过clearHwFlags接口清除添加的华为刘海屏Flag,恢复应用不使用刘海区显示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*刘海屏全屏显示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");
}
}

华为刘海屏flag动态添加和删除代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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());
}
}
});

获取默认和隐藏刘海区开关值接口

隐藏开关打开之后,显示规格


读取开关状态:
1
2
3
public static final String DISPLAY_NOTCH_STATUS = "display_notch_status";
int mIsNotchSwitchOpen = Settings.Secure.getInt(getContentResolver(),DISPLAY_NOTCH_STATUS, 0);
// 0表示“默认”,1表示“隐藏显示区域”