本文由 玉刚说写做平台 提供写做赞助java
原做者:
四月葡萄
android版权声明:本文版权归微信公众号
玉刚说
全部,未经许可,不得以任何形式转载安全
先吐槽一下,刘海屏真丑。然而做为苦逼的开发者,仍是要去适配刘海屏的。好了,吐槽完毕,进入正题。 微信
Google将刘海屏命名为屏幕缺口了,这一小节内容摘自Android官方介绍: 屏幕缺口支持。markdown
Android P 支持最新的全面屏以及为摄像头和扬声器预留空间的凹口屏幕。 经过全新的 DisplayCutout
类,能够肯定非功能区域的位置和形状,这些区域不该显示内容。 要肯定这些凹口屏幕区域是否存在及其位置,请使用 getDisplayCutout()
函数。ide
全新的窗口布局属性 layoutInDisplayCutoutMode
让您的应用能够为设备凹口屏幕周围的内容进行布局。 您能够将此属性设为下列值之一:函数
LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
您能够按以下方法在任何运行 Android P 的设备或模拟器上模拟屏幕缺口:oop
目前Android支持了三类凹口屏幕类型:边角显示屏凹口(斜刘海)、双显示屏凹口(刘海+胡子)、长型显示屏凹口(刘海),以下图所示: 布局
目前的手机主要仍是长型显示屏凹口,即刘海屏。其余斜刘海和胡子手机应该尚未实物吧?反正是亮瞎了狗眼了。post
注意,如下接口都是要Build.VERSION.SDK_INT >= 28
才能调用到。
主要用于获取凹口位置和安全区域的位置等。主要接口以下所示:
方法 | 接口说明 |
---|---|
getBoundingRects() | 返回Rects的列表,每一个Rects都是显示屏上非功能区域的边界矩形。 |
getSafeInsetLeft () | 返回安全区域距离屏幕左边的距离,单位是px。 |
getSafeInsetRight () | 返回安全区域距离屏幕右边的距离,单位是px。 |
getSafeInsetTop () | 返回安全区域距离屏幕顶部的距离,单位是px。 |
getSafeInsetBottom() | 返回安全区域距离屏幕底部的距离,单位是px。 |
来看下例子。 这里将开发者选项中的模拟具备凹口的显示屏选项改成双显示屏凹口,即这里应当有两个刘海,而后,直接上代码:
public class NotchActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //开局就一张背景图 setContentView(R.layout.notch); getNotchParams(); } @TargetApi(28) public void getNotchParams() { 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); } } } }); } } 复制代码
输出结果为:
06-04 21:57:10.120 5698-5698/? E/TAG: 安全区域距离屏幕左边的距离 SafeInsetLeft:0 06-04 21:57:10.120 5698-5698/? E/TAG: 安全区域距离屏幕右部的距离 SafeInsetRight:0 06-04 21:57:10.120 5698-5698/? E/TAG: 安全区域距离屏幕顶部的距离 SafeInsetTop:112 06-04 21:57:10.120 5698-5698/? E/TAG: 安全区域距离屏幕底部的距离 SafeInsetBottom:112 06-04 21:57:10.120 5698-5698/? E/TAG: 刘海屏数量:2 06-04 21:57:10.120 5698-5698/? E/TAG: 刘海屏区域:Rect(468, 0 - 972, 112) 06-04 21:57:10.120 5698-5698/? E/TAG: 刘海屏区域:Rect(468, 2448 - 972, 2560) 复制代码
能够看到,即距离顶部和底部各112px的区域就是安全区域了。
使用例子:
WindowManager.LayoutParams lp = getWindow().getAttributes();
lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
getWindow().setAttributes(lp);
复制代码
Android P中新增了一个布局参数属性layoutInDisplayCutoutMode
,包含了三种不一样的模式,以下所示:
模式 | 模式说明 |
---|---|
LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT | 只有当DisplayCutout彻底包含在系统栏中时,才容许窗口延伸到DisplayCutout区域。 不然,窗口布局不与DisplayCutout区域重叠。 |
LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER | 该窗口决不容许与DisplayCutout区域重叠。 |
LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES | 该窗口始终容许延伸到屏幕短边上的DisplayCutout区域。 |
下面咱们来写个Demo看下这三种模式的显示效果: Demo很简单,就是显示一张背景图,相关背景布局就不贴了,来看下主要的代码:
public class NotchActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //去掉标题 requestWindowFeature(Window.FEATURE_NO_TITLE); //开局就一张背景图 setContentView(R.layout.notch); //全屏显示 getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); WindowManager.LayoutParams lp = getWindow().getAttributes(); //下面图1 lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER; //下面图2 // lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; //下面图3 // lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT; getWindow().setAttributes(lp); } } 复制代码
这里设置为全屏的显示效果,三种模式的结果以下图所示:
能够看到:
LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
模式会让屏幕到延申刘海区域中。LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
模式不会让屏幕到延申刘海区域中,会留出一片黑色区域。LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
模式在全屏显示下跟LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
同样。
咱们再来看看LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
模式在沉浸式状态栏下的效果,代码以下:
public class NotchActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //去掉标题 requestWindowFeature(Window.FEATURE_NO_TITLE); //开局就一张背景图 setContentView(R.layout.notch); //全屏显示 // getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); //沉浸式状态栏 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); WindowManager.LayoutParams lp = getWindow().getAttributes(); lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT; getWindow().setAttributes(lp); } } 复制代码
以下图所示:
能够看到:
当刘海区域彻底在系统的状态栏时,
LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
的显示效果与LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
一致。
因此,当咱们进行刘海屏的适配时,请根据实际状况去使用不一样的layoutInDisplayCutoutMode
。
LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
将刘海区域变成一条黑色边。上面是Android P才有的解决方案,在P以前呢,上面的代码统统都没用。然而咱们伟大的国产厂商在Android P以前(基本都是Android O)就用上了高档大气上档次的刘海屏,因此,这也造就了各大厂商在Android P以前的解决方案百花齐放。下面,咱们来看下主流厂商:华为、vivo、OPPO、小米等所提供的方案。
注:相关的代码都已封装好,能够直接拷贝使用。
使用新增的meta-data
属性android.notch_support
。 在应用的AndroidManifest.xml
中增长meta-data
属性,此属性不只能够针对Application
生效,也能够对Activity
配置生效。 以下所示:
<meta-data android:name="android.notch_support" android:value="true"/> 复制代码
Application
生效,意味着该应用的全部页面,系统都不会作竖屏场景的特殊下移或者是横屏场景的右移特殊处理。Activity
生效,意味着能够针对单个页面进行刘海屏适配,设置了该属性的Activity
系统将不会作特殊处理。实际上还有一种代码实现的方式,不过代码比较多,这里就不贴了,有兴趣的话能够在文末的连接中点进去看看。
经过如下代码便可知道华为手机上是否有刘海屏了,true
为有刘海,false
则没有。
public static boolean hasNotchAtHuawei(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; } } 复制代码
华为提供了接口获取刘海的尺寸,以下:
//获取刘海尺寸:width、height //int[0]值为刘海宽度 int[1]值为刘海高度 public static int[] getNotchSizeAtHuawei(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 (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; } } 复制代码
vivo在设置--显示与亮度--第三方应用显示比例中能够切换是否全屏显示仍是安全区域显示。
public static final int VIVO_NOTCH = 0x00000020;//是否有刘海 public static final int VIVO_FILLET = 0x00000008;//是否有圆角 public static boolean hasNotchAtVoio(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", "hasNotchAtVoio ClassNotFoundException"); } catch (NoSuchMethodException e) { Log.e("Notch", "hasNotchAtVoio NoSuchMethodException"); } catch (Exception e) { Log.e("Notch", "hasNotchAtVoio Exception"); } finally { return ret; } } 复制代码
vivo不提供接口获取刘海尺寸,目前vivo的刘海宽为100dp,高为27dp。
OPPO目前在设置 -- 显示 -- 应用全屏显示 -- 凹形区域显示控制,里面有关闭凹形区域开关。
public static boolean hasNotchInScreenAtOPPO(Context context) { return context.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism"); } 复制代码
OPPO不提供接口获取刘海尺寸,目前其有刘海屏的机型尺寸规格都是统一的。不排除之后机型会有变化。 其显示屏宽度为1080px,高度为2280px。刘海区域则都是宽度为324px, 高度为80px。
系统增长了 property ro.miui.notch
,值为1时则是 Notch 屏手机。
手头上没有小米8的手机,暂时无法验证,这里就不贴代码了,省得误导你们。后面测试过再放出来。
小米的状态栏高度会略高于刘海屏的高度,所以能够经过获取状态栏的高度来间接避开刘海屏,获取状态栏的高度代码以下:
public static int getStatusBarHeight(Context context) { int statusBarHeight = 0; int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); if (resourceId > 0) { statusBarHeight = context.getResources().getDimensionPixelSize(resourceId); } return statusBarHeight; } 复制代码
其余手机也能够经过这个方法来间接避开刘海屏,可是有可能有些手机的刘海屏高度会高于状态栏的高度,因此这个方法获取到的结果并不必定安全。
若是要适配其余厂商的刘海屏,能够去找下他们的开发者文档,通常都会有提供的,这里就不详述了。