一, 需求
- 竖屏有状态栏时由状态栏调整其布局来适配刘海区
- 横屏或竖屏无状态栏时默认情况下由系统平移界面来避开刘海区
- 若要在上述状态下, 显示到刘海区域。 则应用需要适配.
- 需注意在适配刘海区域时, 在设置中 “显示/隐藏” 开关的不同表现:
虽然页面适配后允许应用的内容拓展到刘海区域, 但是仍然要注意不要放置关键信息。 因为在"隐藏刘海"开关打开的情况下, 拓展的内容会被裁剪
二, 应用适配方法(广大app开发者通过此方法适配即可)
1. 适配刘海区域
若要在竖屏无状态栏情况下使用刘海区域。 应用可在页面进行如下配置。
// Android P中已增加适配刘海屏的原生接口支持。 设置此方式只在Android P以下版本有效。
if (Build.VERSION.SDK_INT < 28) {
View decor = getWindow().getDecorView();
decor.setSystemUiVisibility(decor.getSystemUiVisibility() | 0x00000080);
}
//注1: 此逻辑的关键在于为systemUiFlag添加 0x00000080, 请应用在操作时请不要将此字段覆盖。
//注2:据同事反馈在Android M和L中, setSystemUiVisibility与requestWindowFeature共用时,
// 需将setSystemUiVisibility置于requestWindowFeature之后, 否则会闪退。
2. 判断是否刘海设备
// 判断刘海设备
boolean fringeDevice = false;
try {
Class clazz = Class.forName("flyme.config.FlymeFeature");
Field field = clazz.getDeclaredField("IS_FRINGE_DEVICE");
fringeDevice = (boolean) field.get(null);
} catch (Exception e) {
Log.e("ERROR", e.toString());
}
3. 获取刘海区域大小
// 获取刘海高度(51px)
int fringeHeight = 0;
int fhid = getResources().getIdentifier("fringe_height", "dimen", "android");
if (fhid > 0) {
fringeHeight = getResources().getDimensionPixelSize(fhid);
}
// 获取刘海宽度(534px,)
int fringeWidth = 0;
int fwid = getResources().getIdentifier("fringe_width", "dimen", "android");
if (fwid > 0) {
fringeWidth = getResources().getDimensionPixelSize(fwid);
}
4. 判断隐藏刘海开关状态
// 判断隐藏刘海开关(默认关)
boolean isFringeHidden = Settings.Global.getInt(getContentResolver(), "mz_fringe_hide",0) == 1;
5. 监听刘海开关状态
Uri uri = Settings.Global.getUriFor("mz_fringe_hide");
getContentResolver().registerContentObserver(uri, false, new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
boolean isHidden = Settings.Global.getInt(getContentResolver(), "mz_fringe_hide",0) == 1;
//TODO 监听获取
}
});
三, 系统获取方法
1. 判断是否刘海设备
// 判断刘海设备
boolean fringeDevice = flyme.config.FlymeFeature.IS_FRINGE_DEVICE;
2. 获取刘海区域大小
// 获取刘海高度(51px)
int fringeHeight = getResources().getDimensionPixelSize(com.android.internal.R.dimen.fringe_height);
// 获取刘海宽度(534px,)
int fringeWidth = getResources().getDimensionPixelSize(com.android.internal.R.dimen.fringe_width);
3. 判断隐藏刘海开关状态
// 判断隐藏刘海开关(默认关)
boolean isFringeHidden = Settings.Global.getInt(mContext.getContentResolver(),MzSettings.Global.MZ_FRINGE_HIDE, 0) == 1;
四, 系统弹窗
框架对系统弹窗也做了处理。 但是仅限layer层级低于StatusBar的窗口, 具体参见WindowManagerPolicy.java 的 getWindowLayerFromTypeLw 方法中的定义
1. 处理逻辑
这里注意与上面不同:我们为系统弹窗赋予了更高的权限, 系统弹窗若做了适配, 则无视隐藏刘海开关状态, 始终按原始方式显示
2. 适配方法
View view = new View(context);
if (Build.VERSION.SDK_INT < 28) {
view.setSystemUiVisibility( view.getSystemUiVisibility() | 0x00000080);
}
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
windowManager.addView(view, params);
此处需注意对setSystemUiVisibility的设置需位于 addView 之前。 且一经设置, 不容改变
五, 特殊需求
1, 忽略隐藏刘海开关
在我们的原需求中, 如果用户在设置中手动开启了“隐藏刘海”开关。 则无论应用是否(应用二.1中的方法)适配刘海屏, 都会在顶部显示黑色圆角以隐藏刘海。但是在某些重要场景中, 应用希望自行处理隐藏刘海的情况, 如相机, 图库。。
应用可以使用如下方法来屏蔽系统关于刘海开关的相关处理。 (请使用201808241800之后的版本进行验证。 )
if (Build.VERSION.SDK_INT < 28) {
View decor = getWindow().getDecorView();
decor.setSystemUiVisibility(decor.getSystemUiVisibility() | 0x00000080 | 0x00000040);
}
// 0x00000040需要与0x00000080配合使用, 单独使用无效。
注意: 为了保证系统功能的一致性, 请谨慎使用此功能
开发者可以购买 魅族X8 来进行问题的验证
https://item.jd.com/100000827661.html
上述文档仅仅针对与Android8.0的机器有效, Android9.0已经官方公开了刘海屏的接口,相关的适配参考如下:
针对当前android 9.0的刘海屏, 有很多应用出现了界面的平移现象. 这主要是由Android P的原生逻辑来控制的. 所以需要应用对刘海屏进行适配. 适配方法如下
一. 原生判断是否刘海屏方式
由于Android P对异型屏各种情况的考虑, 比如有多处异型区域, 异型区域不规则等等情况. 系统中引入了 DisplayCutout 和 LAYOUT_IN_DISPLAY_CUTOUT_MODE 方便应用去适配. 判断方式如下:
DisplayCutout displayCutout = decorView.getRootWindowInsets().getDisplayCutout();
if (displayCutout != null) {
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);
}
}
} else {
Log.e("TAG", "不是刘海屏!!!!!!!!!!!!!!!!!!!!!!");
}
若在onCreate中获取, 请使用decorView.post(new Runnable() { ....})
二. Flyme判断刘海屏方式
鉴于一方面仍然有许多应用反馈按照原生的方式得到为null,另一方面使用原生的判断方式时机可能比较晚,这里提供flyme的一种判定方式
DisplayManager dy = (DisplayManager) getApplicationContext().getSystemService(Context.DISPLAY_SERVICE);
Display d = dy.getDisplay(Display.DEFAULT_DISPLAY);
int FLAG_DISPLAY_CUTOUT = 1 << 29;
if ((d.getFlags() & FLAG_DISPLAY_CUTOUT) != 0) {
//是刘海屏
} else {
//不是刘海屏
}
二. 异型屏适配.
请各应用按需使用设置
WindowManager.LayoutParams lp = getWindow().getAttributes();
// WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
// WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
// WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
lp.layoutInDisplayCutoutMode =WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
getWindow().setAttributes(lp);
至于使用各模式的效果, 请查看附件demo
- 注意: 对于异型屏的处理, 大多数应用可能会用到LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES 模式, 但是此模式在demo中顶部会有一个白条(Activity). 这时需要应用对页面进行沉浸式处理.
- 本文档较简单, 想要详细的介绍可参阅 https://blog.csdn.net/yi_master/article/details/80309757