Android 屏幕适配之bug 刘海屏、水滴屏

对刘海屏、水滴屏做适配前,先在此给出一个基本概念:何谓刘海屏?何谓水滴屏?
image

上述两种屏幕都可以统称为刘海屏,不过对于右侧较小的刘海,业界一般称为水滴屏或美人尖。

目前国内流行的手机厂商主要有:vivo、oppo、华为、小米。各厂商对刘海屏的适配都大不相同,各自有各自对刘海屏的适配API,具体的适配方法可以阅读相应的官网:
VIVO:https://dev.vivo.com.cn/documentCenter/doc/103

OPPO:https://open.oppomobile.com/wiki/doc#id=10159

小米:https://dev.mi.com/console/doc/detail?pId=1293

华为:https://developer.huawei.com/consumer/cn/devservice/doc/50114?

具体的适配方法这里不作一一介绍,按照以上四大厂商官网所给出的适配方法,这里给出四大厂商判断/获取刘海屏的工具类:

 /**
 * xiaomi、huawei、vivo、oppo流行机型异型屏判断工具类
*/
public class NotchScreenTool {

//刘海屏、水滴屏等异型屏支持的Android系统版本:8.0-》全面屏  8.0以上-》刘海屏、水滴屏等异型屏
public static boolean isNotchSupportVersion(){
    int curApiVersion = Build.VERSION.SDK_INT;
    if(curApiVersion > 26){
        return true;
    }
    return false;
}

//获取手机屏幕的旋转角度
public static int getScreenAngle(Context context){
    return ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getRotation();
}

//检查流行机型是否存在刘海屏
public static boolean isNotch(Context context){
    return isNotch_VIVO(context) || isNotch_OPPO(context) || isNotch_HUAWEI(context) || isNotch_XIAOMI(context);
}

//检查vivo是否存在刘海屏、水滴屏等异型屏
public static boolean isNotch_VIVO(Context context){
    boolean isNotch = false;
    try {
        ClassLoader cl = context.getClassLoader();
        Class cls = cl.loadClass("android.util.FtFeature");
        Method method = cls.getMethod("isFeatureSupport", int.class);
        isNotch = (boolean) method.invoke(cls,0x00000020);//0x00000020:是否有刘海  0x00000008:是否有圆角
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }finally {
        return isNotch;
    }
}

//检查oppo是否存在刘海屏、水滴屏等异型屏
public static boolean isNotch_OPPO(Context context){
    boolean isNotch = false;
    try {
        isNotch = context.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism");
    }catch (Exception e){
        e.printStackTrace();
    }finally {
        return isNotch;
    }
}

//检查huawei是否存在刘海屏、水滴屏等异型屏
public static boolean isNotch_HUAWEI(Context context) {
    boolean isNotch = false;
    try {
        ClassLoader cl = context.getClassLoader();
        Class cls = cl.loadClass("com.huawei.android.util.HwNotchSizeUtil");
        Method method = cls.getMethod("hasNotchInScreen");
        isNotch = (boolean) method.invoke(cls);
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        return isNotch;
    }
}

//检查xiaomi是否存在刘海屏、水滴屏等异型屏
public static boolean isNotch_XIAOMI(Context context){
    boolean isNotch = false;
    try {
        ClassLoader cl = context.getClassLoader();
        Class cls = cl.loadClass("android.os.SystemProperties");
        Method method = cls.getMethod("getInt", String.class, int.class);
        isNotch = ((int) method.invoke(null, "ro.miui.notch", 0) == 1);
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }finally {
        return isNotch;
    }
}

//获取huawei刘海屏、水滴屏的宽度和高度:int[0]值为刘海宽度 int[1]值为刘海高度
public static int[] getNotchSize_HUAWEI(Context context) {
    int[] notchSize = new int[]{0, 0};
    try {
        ClassLoader cl = context.getClassLoader();
        Class cls = cl.loadClass("com.huawei.android.util.HwNotchSizeUtil");
        Method method = cls.getMethod("getNotchSize");
        notchSize = (int[]) method.invoke(cls);
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        return notchSize;
    }
}

//获取xiaomi刘海屏、水滴屏的宽度和高度:int[0]值为刘海宽度 int[1]值为刘海高度
public static int[] getNotchSize_XIAOMI(Context context){
    int[] notchSize = new int[]{0,0};
    if(isNotch_XIAOMI(context)) {
        int resourceWidthId = context.getResources().getIdentifier("notch_width", "dimen", "android");
        if (resourceWidthId > 0) {
            notchSize[0] = context.getResources().getDimensionPixelSize(resourceWidthId);
        }
        int resourceHeightId = context.getResources().getIdentifier("notch_height", "dimen", "android");
        if (resourceHeightId > 0) {
            notchSize[1] = context.getResources().getDimensionPixelSize(resourceHeightId);
        }
    }
    return notchSize;
}

//获取vivo、oppo刘海屏、水滴屏的高度:官方没有给出标准的获取刘海高度的API,由于大多情况是:状态栏≥刘海,因此此处获取刘海高度采用状态栏高度
public static int getNotchHeight(Context context){
    int notchHeight = 0;
    if(isNotch_VIVO(context) || isNotch_OPPO(context)) {
        //若不想采用状态栏高度作为刘海高度或者可以采用官方给出的刘海固定高度:vivo刘海固定高度:27dp(need dp2px)  oppo刘海固定高度:80px
        int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
        if (resourceId > 0) {
            notchHeight = context.getResources().getDimensionPixelSize(resourceId);
        }
    }
    return notchHeight;
}

//dp转px
private int dp2px(Context context,float dpValue){
    float scale=context.getResources().getDisplayMetrics().density;
    return (int)(dpValue*scale+0.5f);
}

}

若需要对厂商进行判断可以使用:
 //判断手机厂商:华为、小米、oppo、vivo
 String brand =android.os.Build.BRAND.toLowerCase();
if("huawei".equals(brand)){
 //...
}else if("xiaomi".equals(brand)){
//...
}else if("vivo".equals(brand)){
//...
}else if("oppo".equals(brand)){
//...
}

根据四大厂商官网所提供的适配方案,其中需要在AndroidManifest中添加标签(具体说明请浏览官网):

  <!-- 适配全面屏 Android O vivo&oppo-->
  <meta-data android:name ="android.max_aspect" android:value ="2.2" />
 <!-- 适配刘海屏、水滴屏 Android O 小米 -->
 <meta-data android:name="notch.config" android:value="portrait|landscape"/>
<!-- 适配刘海屏、水滴屏 Android O 华为 -->
<meta-data android:name="android.notch_support" android:value="true"/>

在对于Android P的适配中Google给出了统一的方案(基于Android API 28):

1、AndroidManifest中添加标签:


<meta-data android:name="android.vendor.full_screen" android:value="true"/>

2、把TargetVersion提到28,miniVersion提到23

3、刘海屏判别方法

在Build.VERSION.SDK_INT >= 28中提供了以下接口:

DisplayCutout类接口:主要用于获取凹口位置和安全区域的位置等。

方法

*getBoundingRects()

接口 说明: 返回Rects的列表,每个Rects都是显示屏上非功能区域的边界矩形。

*getSafeInsetLeft()

返回安全区域距离屏幕左边的距离,单位是px。

*getSafeInsetRight()

返回安全区域距离屏幕右边的距离,单位是px。

*getSafeInsetTop()

返回安全区域距离屏幕顶部的距离,单位是px。

*getSafeInsetBottom()

返回安全区域距离屏幕底部的距离,单位是px。

 final View decorView = getWindow().getDecorView();
decorView.post(new Runnable() {
@Override
public void run() {
    DisplayCutout displayCutout = decorView.getRootWindowInsets().getDisplayCutout();
    Log.e("TAG", "安全区域距离屏幕左边的距离 SafeInsetLeft:" + displayCutout.getSafeInsetLeft());
    Log.e("TAG", "安全区域距离屏幕右部的距离 SafeInsetRight:" + displayCutout.getSafeInsetRight());
    Log.e("TAG", "安全区域距离屏幕顶部的距离 SafeInsetTop:" + displayCutout.getSafeInsetTop());
    Log.e("TAG", "安全区域距离屏幕底部的距离 SafeInsetBottom:" + displayCutout.getSafeInsetBottom());
    List<Rect> rects = displayCutout.getBoundingRects();
    if (rects == null || rects.size() == 0) {
        Log.e("TAG", "不是刘海屏");
    } else {
        Log.e("TAG", "刘海屏数量:" + rects.size());
        for (Rect rect : rects) {
            Log.e("TAG", "刘海屏区域:" + rect);
        }
    }
 }
});
ndroid P中新增了一个布局参数属性layoutInDisplayCutoutMode,包含了三种不同的模式,如下所示:

模式:

*LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT

模式说明:只有当DisplayCutout完全包含在系统栏中时,才允许窗口延伸到DisplayCutout区域。 否则,窗口布局不与DisplayCutout区域重叠

*LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER

该窗口决不允许与DisplayCutout区域重叠。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容

  • 防蓝光眼镜人人适用吗? 我们每天的生活离不开手机,电脑,ipad……很多人的视力也因为长时间接触强光电子屏幕而走下...
    晓夜子谈阅读 385评论 0 0
  • mac pro 小房子图标home下建立自己的文件夹.侧边栏显示磁盘,mac已经划分好,有各类文件,下载,应用等....
    WhoN阅读 219评论 0 1
  • 乌合之众,作者古斯塔夫・勒庞 Gustave Le Bon(1841-1931) 法国著名社会心理学家。他自189...
    请叫我酒窝妹阅读 189评论 0 0
  • 瘦人碗子阅读 366评论 2 2
  • 最恐怖的鬼故事排行榜最新出炉,此次带来的最恐怖的鬼故事前五名,都是公认的最恐怖的鬼故事。在世界上本没有鬼,但是鬼故...
    分享者_阅读 2,871评论 4 17