1. 背景

全面屏手机已成为当下的主流趋势,高占比的屏幕能够为用户带来极致的体验,但是全面屏手机给用户带来极致体验的同时也给 App 的适配带来很大的挑战,未适配全面屏的 App 会遇到各种显示问题。刘海屏是非全面屏到全面屏转变过程中,整个产业给出的一种解决方案,刘海区是为了前置电子元器件(如前置摄像头、扬声器、距离感应器等)而不得不做出的技术妥协。iOS 系统从 iOS11 开始支持刘海屏,而 Android 系统直到 Android P 才提供了刘海屏的官方支持,国内 Top 厂商(华为,小米,OPPO,VIVO)为了尽早推出自己的高屏占比手机也纷纷在 Android O 开始提供了自己的定制化刘海区实现方案,进一步提高了适配的工作量。目前主流的刘海区方案如下图所示:

2. 没有适配刘海屏会出现什么情况?

  1. 内容区下移,头部出现黑条 2. 顶部被刘海区遮挡

3. UI 适配方案

我们先看一下 Google 官方的刘海规格图:

l12.png

根据上图可知刘海屏适配的关键点就是让 App 的重要内容避开危险区域,具体就是中间黑色的凹槽区域因此我们适配可分为以下三步:

  1. 首先判断是否为刘海屏手机
  2. 判断布局是否延伸到刘海区域
  3. 调整布局保证重要内容避开刘海区域

3. Top 厂商 Android O 刘海屏接口

1. 华为

判断是否刘海屏接口:

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 (Exception e) {
    } finally {
        return ret;
    }
}复制代码

获取刘海尺寸接口:

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 (Exception e) {
    } finally {
        return ret;
    }
}复制代码

2. 小米

判断是否刘海屏接口:

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

获取刘海尺寸接口:

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

3. OPPO

判断是否刘海屏接口:

context.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism");复制代码

获取刘海尺寸接口(由于 OPPO 没有提供获取刘海屏尺寸的接口所以我们直接获取状态高度就可以了,因为刘海区域肯定是小于等于状态栏高度的):

public static int getNotchHeight(Context context) {
    int statusBarHeight = 0;
    try {
        int resourceId = context.getResources().getIdentifier(
                "status_bar_height", "dimen", "android");
        if (resourceId > 0) {
            statusBarHeight = context.getResources()
                    .getDimensionPixelSize(resourceId);
        }
    } catch (Exception e) {
    }
    return statusBarHeight;
}复制代码

4. VIVO

判断是否刘海屏接口:

public boolean hasNotchInScreen(Context context) {
        booblen hasNotch = false;
        try {
            ClassLoader cl = context.getClassLoader();
            @SuppressLint("PrivateApi") Class ftFeature = cl.loadClass("android.util.FtFeature");
            Method[] methods = ftFeature.getDeclaredMethods();
            for (Method method : methods) {
                if (method.getName()
                        .equalsIgnoreCase("isFeatureSupport")) {
                    hasNotch = (boolean) method
                            .invoke(ftFeature, NOTCH_IN_SCREEN_VOIO);
                    break;
                }
            }
        } catch (Exception ignored) {
        }
    }
    return hasNotch;
}复制代码

获取刘海尺寸接口(由于 VIVO 没有提供获取刘海屏尺寸的接口所以我们直接获取状态高度就可以了,因为刘海区域肯定是小于等于状态栏高度的):

public static int getNotchHeight(Context context) {
    int statusBarHeight = 0;
    try {
        int resourceId = context.getResources().getIdentifier(
                "status_bar_height", "dimen", "android");
        if (resourceId > 0) {
            statusBarHeight = context.getResources()
                    .getDimensionPixelSize(resourceId);
        }
    } catch (Exception e) {
    }
    return statusBarHeight;
}复制代码

4. Android P 刘海屏适配方案

判断是否刘海屏接口,需要注意的是,这里的 attachedView 不能在 onCreate 的时候随便传一个 view,那时候 view 还没有 Attach 到 window,拿到的 RootWindowInsets 是 null:

public boolean hasNotchInScreen(View attachedView) {
    boolean hasNotch = false;
	WindowInsets windowInsets = attachedView.getRootWindowInsets();
	if (windowInsets != null) {
	    DisplayCutout displayCutout = windowInsets.getDisplayCutout();
	    if (displayCutout != null) {
	        List<Rect> rects = displayCutout.getBoundingRects();
	        if (rects != null && rects.size() > 0) {
	            hasNotch = true;
	        }
	    }
	}
	return hasNotch;
}复制代码

获取刘海尺寸接口:

DisplayCutout 类中就已经包含了刘海区域信息,所以在在 hasNotchInScreen 中就可以保存下来后面使用

让页面延伸到刘海区域 WindowManager.LayoutParams.layoutInDisplayCutoutMode:

// DEFAULT LayoutFullScrren可以侵入,其余不会侵入刘海区域
public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT = 0;
// 这个状态是永远不会在刘海上绘制
public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER = 2;
// 使用这个状态可以让LayoutFullScreen和FullScrren的时候在刘海区域绘制
public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES = 1;复制代码
启动页黑条:
启动页设置了默认背景,并且设置了 Fullscreen,那么在启动的时候刘海会是一个大黑条,解决的方法是在 Launcher Activity 的 theme 中加入 <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>

5. 全面屏适配

方案 1:
AndroidManifest.xml 文件添加属性: <meta-data android:name="android.max_aspect" android:value="2.4" />
应用适配建议采用 meta-data 的方式,具体可以参考:developer.android.com/guide/pract…

方案 2:
添加 android:resizeableActivity =“true”
此设置只针对 Activity 生效,且增加了此属性该 activity 也会支持分屏显示。

方案 3:
修改 AndroidManifest.xml 文件,设置 targetSdkVersion>=26,就是应用升级到 O 版本,不需要设置其他任何属性,默认在任何纵横比的屏幕都能全屏显示。(备注:有一种例外情况需要注意,应用如果已经适配到 O 版本,并且通过 meta-data 属性 android.max_aspect 或者是 android:MaxAspectRatio 属性设置了页面支持的最大纵横比,同时又通过 android:resizeableActivity=“false”设置了页面不支持分屏,这个时候系统会按照应用自己设置的最大纵横比决定该页面是否能全屏显示,如果应用设置的最大纵横比比手机屏幕比例小,那应用还是无法全屏显示。)

码字不易,如有建议请扫码


  • Android

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

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