NotchTools
💥💥Android刘海屏适配方案---NotchTools,适配国内四大厂商(华为、小米、oppo、vivo)刘海屏手机,根据自己业务需求,提供多种接入方式适配刘海屏。
Install / Use
/learn @zhangzhun132/NotchToolsREADME

1、概述
刘海屏指的是手机屏幕上方由于追求极致边框而采用的方案,表现为在顶部有块黑色遮挡,长得像刘海,所以叫刘海屏。
目前google在Android P上已经对刘海屏的适配进行了统一,所以在targetApi >= 28上可以使用谷歌官方推荐的适配方案进行刘海屏适配。但是在Android O版本的刘海屏如何适配呢?这就是本文要重点阐述的内容了:
1、对国内四大厂商(华为、小米、OPPO、VIVO)对Android O 版本刘海屏的适配方案进行介绍; 2、对Android P版本的刘海屏进行适配; 3、提出对Android O 版本刘海屏的通用解决方案,包括全屏占用刘海屏、全屏不占用刘海屏两种情况; 4、提出适配工具NotchTools解决方案,让你的应用简单快捷的适配全面屏
2、适配与未适配的效果对比
因为相比普通常规手机而言,刘海屏顶部中间会突出一块刘海区域,所以会在给Actiivty设置全屏Flag的时候有一些不同。本文所涉及到的刘海屏适配都是在给Activity的window设置SYSTEM_UI_FLAG_FULLSCREEN(全屏flag)前提下的,在显示状态栏的情况下(不管是状态栏透明或者不透明),不是本文讨论的核心,我们的所说的刘海屏适配只是针对全屏沉浸式(状态栏隐藏)的情况下。
在设置SYSTEM_UI_FLAG_FULLSCREEN了Flag后,国内厂商的刘海屏手机对于此表现的默认显示效果都是有差异的,具体为: 1、华为手机默认是全屏但是不占用刘海区域; 2、小米手机默认是全屏但是不占用刘海区域; 3、oppo手机默认是全屏且占用刘海区域,可在设置里单独给APP设置是否隐藏刘海; 4、vivo手机默认是全屏且占用刘海区域,可在设置里单独给APP设置是否隐藏刘海;
备注:通过实验及查阅文档发现,华为和小米适配方案是类似的且适配方案成熟,oppo、和vivo的适配介绍是类似的,且基本无适配方案。华为、小米会分别对全屏占用刘海、全屏不占用刘海两种情况分别提供了方法。而oppo、vivo只提供了是否有刘海这个一个方法,并没有提供适配方案。
所以我们再全屏的情况下需要对四大厂商做下适配,不然有可能一个App在不同手机上表现不一致、或者会对UI做了截断,影响使用体验:

3、适配方案
Android O的刘海屏适配方案可分为两种情况:
3、1 全屏且占用刘海屏的适配方案
对于需要全屏且占用刘海屏显示的情况,如沉浸式游戏、沉浸式阅读(需要把态栏隐藏),适配时可以采用如下步骤: 1、在Activity中使用setSystemUiVisibility设置全屏的一些标识; 2、根据不同厂商的适配规则(官网有提供)设置不同的flag(大都通过反射),来让App全屏沉浸式显示; 3、根据厂商提供的Api,获取刘海的高度,来调节一些View的间距,达到适配目的。
3、2 全屏但不占用刘海的适配方案
这种适配方案一般采用如下步骤: 1、去各大手机厂商官网找到对应的全屏但不占用刘海的方案,目前只有小米、华为提供了具体方法来设置是否占用刘海区域,oppo和vivo只提供了机型是否是刘海屏手机的方法,但未提供适配方案; 2、华为和小米都有具体方案来适配全屏不占用刘海的情况,这里主要对vivo和oppo进行适配。 这里要说明下,在系统设置里可以设置刘海是否隐藏,不过华为、小米是全局设置,而ov是可以单独对app进行设置,又因为ov没有提供具体的适配方案,所以对于ov的全屏适配达不到完美的适配,在不占用刘海时顶部会留出一块黑边。
3.3 华为手机刘海屏适配方案

3.3.1 判断华为手机是否为刘海屏手机
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public boolean isNotchScreen(Window window) {
boolean isNotchScreen = false;
try {
ClassLoader cl = window.getContext().getClassLoader();
Class HwNotchSizeUtil = cl.loadClass("com.huawei.android.util.HwNotchSizeUtil");
Method get = HwNotchSizeUtil.getMethod("hasNotchInScreen");
isNotchScreen = (boolean) get.invoke(HwNotchSizeUtil);
} catch (ClassNotFoundException e) {
LogUtils.d(TAG, "hasNotchInScreen ClassNotFoundException");
} catch (NoSuchMethodException e) {
LogUtils.d(TAG, "hasNotchInScreen NoSuchMethodException");
} catch (Exception e) {
LogUtils.d(TAG, "hasNotchInScreen Exception");
} finally {
return isNotchScreen;
}
}
3.3.2 获取华为手机的刘海屏高度
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public int getNotchHeight(Window window) {
if (!isNotchScreen(window)) {
return 0;
}
int[] ret = new int[]{0, 0};
try {
ClassLoader cl = window.getContext().getClassLoader();
Class HwNotchSizeUtil = cl.loadClass("com.huawei.android.util.HwNotchSizeUtil");
Method get = HwNotchSizeUtil.getMethod("getNotchSize");
ret = (int[]) get.invoke(HwNotchSizeUtil);
} catch (ClassNotFoundException e) {
} catch (NoSuchMethodException e) {
} catch (Exception e) {
} finally {
return ret[1];
}
}
3.3.3 设置页面在华为刘海屏手机使用刘海区
@TargetApi(Build.VERSION_CODES.KITKAT)
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 (ClassNotFoundException | NoSuchMethodException | IllegalAccessException |InstantiationException
| InvocationTargetException e) {
Log.e("test", "hw add notch screen flag api error");
} catch (Exception e) {
Log.e("test", "other Exception");
}
}
3.3.4 设置页面在华为刘海屏手机不使用刘海区
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
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(WindowManager.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.4 小米手机刘海屏适配方案
3.4.1 判断小米手机是否为刘海屏手机
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public boolean isNotchScreen(Window window) {
return "1".equals(SystemProperties.getInstance().get("ro.miui.notch"));
}
3.4.2 获取小米手机刘海屏高度
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public int getNotchHeight(Window window) {
if (!isNotchScreen(window)) {
return 0;
}
int result = 0;
if (window == null) {
return 0;
}
Context context = window.getContext();
if (isHideNotch(window.getContext())) {
result = NotchStatusBarUtils.getStatusBarHeight(context);
} else {
result = getRealNotchHeight(context);
}
return result;
}
3.4.3 设置页面在小米刘海屏手机使用刘海区
在 WindowManager.LayoutParams 增加 extraFlags 成员变量,用以声明该 window 是否使用耳朵区,其中,extraFlags 有以下变量:
0x00000100 开启配置
0x00000200 竖屏配置
0x00000400 横屏配置
组合后表示 Window 的配置,如:
0x00000100 | 0x00000200 竖屏绘制到耳朵区
0x00000100 | 0x00000400 横屏绘制到耳朵区
0x00000100 | 0x00000200 | 0x00000400 横竖屏都绘制到耳朵区
控制 extraFlags 时注意只控制这几位,不要影响其他位。可以用 Window 的 addExtraFlags 和 clearExtraFlags 来修改, 这两个方法是 MIUI 增加的方法,需要反射调用。
设置页面在小米刘海屏手机使用刘海区代码如下:
public void fullScreenUseStatus(Activity activity, OnNotchCallBack notchCallBack) {
super.fullScreenUseStatus(activity, notchCallBack);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isNotchScreen(activity.getWindow())) {
//开启配置
int FLAG_NOTCH = 0x00000100 | 0x00000200 | 0x00000400;
try {
Method method = Window.class.getMethod("addExtraFlags", int.class);
if (!method.isAccessible()) {
method.setAccessible(true);
}
method.invoke(activity.getWindow(), FLAG_NOTCH);
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.4.4 设置页面在小米刘海屏手机不使用刘海区
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public void fullScreenDontUseStatus(Activity activity, OnNotchCallBack notchCallBack) {
super.fullScreenDontUseStatus(activity, notchCallBack);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isNotchScreen(activity.getWindow())) {
//开启配置
int FLAG_NOTCH = 0x00000100 | 0x00000400;
try {
Method method = Window.class.getMethod("addExtraFlags", int.class);
if (!method.isAccessible()) {
method.setAccessible(true);
}
method.invoke(activity.getWindow(), FLAG_NOTCH);
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.4.5 小米刘海屏手机上刘海屏高度与状态栏高度差异
由于 Notch 设备的状态栏高度与正常机器不一样,因此在需要使用状态栏高度时,不建议写死一个值,而应该改为读取系统的值。
以下是获取当前设备状态栏高度的方法:
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = context.getResources().getDimensionPixelSize(resourceId);
}
以下是获取当前设备刘海高度的方法:
int resourceId = context.getResources().getIdentifier("notch_height", "dimen", "android");
if (resourceId > 0) {
result = context.getResources().getDimensionPixelSize(resourceId);
}
3.5 OPPO手机刘海屏适配方案

3.5.1 判断OPPO手机是否为刘海屏手机
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public boolean isNotchScreen(Window window) {
if (window == null) {
return false;
}
return window.getContext().getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism");
}
3.5.2 获取OPPO手机刘海屏高度
目前OPPO刘海屏适配的官网上并没有给出获取刘海高度的方法,也没有给出占用刘海区域、不占用刘海区域的方法。 官网上给的图上的刘海的固定高度是80px,这里通过获取状态栏高度的方法得到值也是80,大概猜测OPPO手机的刘海高度是和状态栏高度一样的。
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public int getNotchHeight(Window
Related Skills
node-connect
344.4kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
99.2kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
344.4kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
344.4kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
