什么是刘海屏

屏幕的正上方居中位置(上图黑色区域)会被挖掉一个孔,屏幕被挖掉的区域无法正常显示内容,这种类型的屏幕就是刘海屏,也有其他叫法:挖孔屏、凹凸屏等等,为便于说明,后文提到的「刘海屏」都同时指代上图两种屏幕。Apple 一直在引领设计的潮流,自从 iPhone X 发布之后,”刘海屏” 就一直存在争议,本以为是一个美丽的错误(Bug), 却造就了一时间“刘海屏”的模仿潮,由于 Android 在 9.0 之前没有给出官方指导,所以出现了各个厂商自扫门前雪的状况。本文主要讲述 Android 8.0 在各个手机上的适配方案,包括小米、Oppo/ViVo、华为、三星等主流机型,并在文末提及 Android P 统一适配方案。

小米 MIUI Notch 屏适配说明

目前已上市的小米 Notch 设备(俗称刘海屏手机)如下,其宽度、高度和形状均略有差异

机型

model

device

分辨率

Notch高度

Notch宽度

DPI

小米 8

MI 8

dipper

1080*2248

89

560

440

小米 8 SE

MI 8 SE

sirius

1080*2244

85

540

440

小米 8 透明探索版

MI 8 Explorer Edition

ursa

1080*2248

89

560

440

小米8 屏幕指纹版

MI 8 UD

equuleus

1080*2248

89

560

440

小米8 青春版

MI8Lite

platina

1080*2280

82

296

440

小米POCO F1

POCO F1

beryllium

1080*2246

86

588

440

红米 6 Pro

Redmi 6 Pro

sakura

1080*2280

89

352

440

注:以上设备,由于 MIUI 调整了 DPI 值,因此 DP 值与像素值的转换关系是 1dp = 2.75 px 。

如何判断设备为 Notch 机型

系统增加了 property ro.miui.notch,值为 1 时则是 Notch 屏手机。

SystemProperties.getInt("ro.miui.notch", 0) == 1;复制代码

如何获取 Notch / 凹口 / 刘海 的高度和宽度(截至 2018.6.26)

MIUI 10 新增了获取刘海宽和高的方法,需升级至 8.6.26 开发版及以上版本。

以下是获取当前设备刘海高度的方法:

int resourceId = context.getResources().getIdentifier("notch_height", "dimen", "android");
if (resourceId > 0) {
result = context.getResources().getDimensionPixelSize(resourceId);
}复制代码

以下是获取当前设备刘海宽度的方法:

int resourceId = context.getResources().getIdentifier("notch_width", "dimen", "android");
if (resourceId > 0) {
result = context.getResources().getDimensionPixelSize(resourceId);
}复制代码

注意上述获取宽高的方法仅限于 2018.6.26 的以上的版本,那么如何判断小米的 MIUI 的版本 ?

/**
 * 判断是否大于 1529942400 ,2018.6.26 转为毫秒是 1529942400 ,MIUI10 的版本Version codetime
 * @return
 */
public boolean isGreaterMIUI10() {
    return  (Long.parseLong(checkMIUI()) >= 1529942400);
}

public static String checkMIUI() {
String versionCode = "";
String manufacturer = Build.MANUFACTURER;
String model = Build.MODEL;
Logger.d(TAG,"Build.MANUFACTURER = “ + manufacturer + ” ,Build.MODEL = " + Build.MODEL);
if (!TextUtils.isEmpty(manufacturer) && manufacturer.equals(“Xiaomi”)) {
versionCode = getSystemProperty(“ro.miui.version.code_time”);
}
return versionCode;
}复制代码

2018.6.26 的以下的版本参考 1.3.

状态栏高度获取方法

由于 Notch 设备的状态栏高度与正常机器不一样,因此在需要使用状态栏高度时,不建议写死一个值,而应该改为读取系统的值。

以下是获取当前设备状态栏高度的方法:

int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = context.getResources().getDimensionPixelSize(resourceId);
}复制代码

Oppo 凹形屏适配说明

如何判断设备为刘海屏 机型

本次凹形屏规格的机型型号:
PAAM00
PAAT00
PACM00
PACT00
CPH1831
CPH1833
上述机型的屏幕规格完全相同,不需分别做差异化处理,统一适配即可。

context.getPackageManager().hasSystemFeature(“com.oppo.feature.screen.heteromorphism”),返回 true 为凹形屏 ,可识别 OPPO 的手机是否为凹形屏。

publicstaticbooleanhasNotchOPPO(Context context){
        return context.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism");
}复制代码

如何获取 Notch / 凹口 / 刘海 的高度和宽度

OPPO 不提供接口获取刘海尺寸,目前其有刘海屏的机型尺寸规格都是统一的。

采用宽度为 1080px, 高度为 2280px 的圆弧显示屏。 屏幕顶部凹形区域不能显示内容,宽度为 324px, 高度为 80px。

图左:全屏显示示意图,绿色区域为可显示区域
图右:16:9 显示示意图,绿色区域为可显示区域

VIVO 全面屏应用适配指南

如何判断是刘海屏机型

publicstaticfinalint VIVO_NOTCH = 0x00000020;// 是否有刘海 publicstaticfinalint VIVO_FILLET = 0x00000008;// 是否有圆角 publicstaticbooleanhasNotchAtVivo(Context context){
        boolean ret = false;
        try {
            ClassLoader classLoader = context.getClassLoader();
            Class FtFeature = classLoader.loadClass("android.util.FtFeature");
            Method method = FtFeature.getMethod("isFeatureSupport", int.class);
            ret = (boolean) method.invoke(FtFeature, VIVO_NOTCH);
        } catch (ClassNotFoundException e) {
            Log.e("Notch", "hasNotchAtVivo ClassNotFoundException");
        } catch (NoSuchMethodException e) {
            Log.e("Notch", "hasNotchAtVivo NoSuchMethodException");
        } catch (Exception e) {
            Log.e("Notch", "hasNotchAtVivo Exception");
        } finally {
            return ret;
        }
    }复制代码

如何获取 Notch / 凹口 / 刘海 的高度和宽度

vivo 不提供接口获取刘海尺寸,目前 vivo 的刘海宽为 100dp, 高为 27dp。同样建议使用 2.2 中状态栏高度的方式适配。

华为 刘海屏手机安卓 O 版本适配指导

华为的文档是比较标准的,更为贴近 Android P 官方提供的方案。

如何判断是否有刘海屏

类文件 接口 接口说明
com.huawei.android.util.HwNotchSizeUtil public static boolean hasNotchInScreen()

是否是刘海屏手机:
true:是刘海屏;false:非刘海屏。

publicstaticbooleanhasNotchAtHuawei(Context context){
        boolean ret = false;
        try {
            ClassLoader classLoader = context.getClassLoader();
            Class HwNotchSizeUtil = classLoader.loadClass("com.huawei.android.util.HwNotchSizeUtil");
            Method get = HwNotchSizeUtil.getMethod("hasNotchInScreen");
            ret = (boolean) get.invoke(HwNotchSizeUtil);
        } catch (ClassNotFoundException e) {
            Log.e("Notch", "hasNotchAtHuawei ClassNotFoundException");
        } catch (NoSuchMethodException e) {
            Log.e("Notch", "hasNotchAtHuawei NoSuchMethodException");
        } catch (Exception e) {
            Log.e("Notch", "hasNotchAtHuawei Exception");
        } finally {
            return ret;
        }
    }复制代码

如何获取 刘海 的高度和宽度

类文件 接口 接口说明
com.huawei.android.util.HwNotchSizeUtil public static int[] getNotchSize()

获取刘海尺寸:width、height

int[0] 值为刘海宽度 int[1] 值为刘海高度。

// 获取刘海尺寸:width、height//int[0] 值为刘海宽度 int[1] 值为刘海高度 publicstaticint[] getNotchSizeAtHuawei(Context context) {
        int[] ret = newint[]{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("Notch", "getNotchSizeAtHuawei ClassNotFoundException");
        } catch (NoSuchMethodException e) {
            Log.e("Notch", "getNotchSizeAtHuawei NoSuchMethodException");
        } catch (Exception e) {
            Log.e("Notch", "getNotchSizeAtHuawei Exception");
        } finally {
            return ret;
        }
    }复制代码

三星

/**
 * 三星手机SM-G8870 刘海屏适配
 *
 */
@RequiresApi(api = Build.VERSION_CODES.O)
public void getSamsungCutOutInfo() {
    try {
        final View decorView = mActivity.getWindow().getDecorView();
        WindowInsets windowInsets = decorView.getRootWindowInsets();
    WindowInsetsWrapper wrapper = new WindowInsetsWrapper(windowInsets);
    DisplayCutoutWrapper mLastDisplayCutoutWrapper = wrapper.getDisplayCutoutWrapper();
    <span class="hljs-keyword">if</span> (mLastDisplayCutoutWrapper == null) {
        isSamsungCutOutMode = <span class="hljs-literal">false</span>;
        <span class="hljs-built_in">return</span>;
    }
    List&lt;Rect&gt; boundingRects = mLastDisplayCutoutWrapper.getBoundingRects();

    <span class="hljs-keyword">if</span> (boundingRects == null || boundingRects.size() == 0) {
        Logger.d(TAG, <span class="hljs-string">"is not notch screen"</span>);
        isSamsungCutOutMode = <span class="hljs-literal">false</span>;
    } <span class="hljs-keyword">else</span> {
        isSamsungCutOutMode = <span class="hljs-literal">true</span>;
        Logger.d(TAG, <span class="hljs-string">"Top="</span> + mLastDisplayCutoutWrapper.getSafeInsetTop());
        Logger.d(TAG, <span class="hljs-string">"Bottom="</span> + mLastDisplayCutoutWrapper.getSafeInsetBottom());
        Logger.d(TAG, <span class="hljs-string">"Left="</span> + mLastDisplayCutoutWrapper.getSafeInsetLeft());
        Logger.d(TAG, <span class="hljs-string">"Right="</span> + mLastDisplayCutoutWrapper.getSafeInsetRight());
        mSamsungSafeInsetHeight = mLastDisplayCutoutWrapper.getSafeInsetLeft() + mLastDisplayCutoutWrapper.getSafeInsetTop();
    }
} catch (Exception e) {
    e.printStackTrace();
}

}复制代码

谷歌 P 版本刘海屏适配方案

特性介绍

谷歌称刘海屏为凹口屏以及屏幕缺口支持, 下面的内容摘自:developer.android.com/preview/fea…

l10.png

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 的设备或模拟器测试凹口屏幕周围的内容显示。

接口介绍

1. 获取刘海尺寸相关接口:

developer.android.com/reference/a…

所属类 方法 接口说明
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. 设置是否延伸到刘海区显示接口:

developer.android.com/reference/a…

属性 属性说明
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

developer.android.com/reference/a…

常量 常量说明
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 区域。

参考实现代码

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。

属性:

l11.PNG

不使用 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)复制代码

参考文档

1、小米MIUI Notch 屏适配说明

2、Oppo 凹形屏适配说明

3、VIVO 全面屏应用适配指南

4、华为 刘海屏手机安卓 O 版本适配指导

5、Google 官网 developer.android.com/preview/fea…

6、Android 刘海屏适配全攻略


  • Android

    开放手机联盟(一个由 30 多家科技公司和手机公司组成的团体)已开发出 Android,Android 是第一个完整、开放、免费的手机平台。

    293 引用
感谢    赞同    分享    收藏    关注    反对    举报    ...