Android O, P 刘海屏及全面屏适配

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”设置了页面不支持分屏,这个时候系统会按照应用自己设置的最大纵横比决定该页面是否能全屏显示,如果应用设置的最大纵横比比手机屏幕比例小,那应用还是无法全屏显示。)

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


猜你喜欢

转载自juejin.im/post/5c514f626fb9a049ad77764a