关于挖孔屏的适配,网络上已经有很多完整教程写的很好,请自行搜索。 本文的重点是总结归纳在这个过程中一些容易被忽略的小疑问和小细节,希望对大家能有所帮助。
这几年手机异形屏越来越火,已经成为时尚潮流。如下图所示举例,依次为挖孔屏,水滴屏,刘海屏。
一、挖孔屏适配
1 概要
2 解决方案
各个手机厂商几乎都有设计自己的异形屏适配解决方案。 从android P开始,Google提供了统一的适配方案(Cutout API),推荐采用Google推荐的统一方案进行适配。
2.1 特性介绍
Google从Android P开始支持最新的全面屏以及为摄像头和扬声器预留空间的挖孔屏幕。通过全新的 DisplayCutout 类,可以确定非功能区域的位置和形状,这些区域不应显示内容。要确定这些挖孔屏幕区域是否存在及其位置,请使用getDisplayCutout()函数。 全新的窗口布局属性layoutInDisplayCutoutMode让您的应用可以为设备挖孔屏幕周围的内容进行布局。
可以将此属性设为下列值之一:
LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
2.2 相关接口
(1)获取挖孔尺寸相关接口 https://developer.android.com/reference/android/view/DisplayCutout
方法 | 接口说明 |
---|---|
List getBoundingRects() | 返回Rects的列表,每个Rects都是显示屏上非功能区域的边界矩形。 |
int getSafeInsetBottom() | 返回安全区域距离屏幕底部的距离,单位是px。 |
int getSafeInsetLeft () | 返回安全区域距离屏幕左边的距离,单位是px。 |
int getSafeInsetRight () | 返回安全区域距离屏幕右边的距离,单位是px。 |
int getSafeInsetTop () | 返回安全区域距离屏幕顶部的距离,单位是px。 |
(2)设置是否延伸到挖孔区显示接口
方法 | 接口说明 |
---|---|
int layoutInDisplayCutoutMode | 默认值:LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT(只有当DisplayCutout完全包含在系统状态栏中时,才允许窗口延伸到DisplayCutout区域显示。) 其他可能取值: LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES(该窗口始终允许延伸到屏幕短边上的DisplayCutout区域。) LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER(该窗口决不允许与DisplayCutout区域重叠。) |
3 UI适配
3.1 判断是否为挖孔屏幕
通过获取DisplayCutout判断是否为挖孔屏幕。实际使用时,还常同时判断安卓版本。
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
- {
- try
- {
- WindowInsets windowInsets = getWindow().getDecorView().getRootWindowInsets();
- if (windowInsets != null)
- {
- cutoutDisp = windowInsets.getDisplayCutout();
- }
- else
- {
- Log.e(TAG_CUTOUT, "windowInsets is null");
- }
- }
- catch (Exception e)
- {
- Log.e(TAG_CUTOUT, "error:"+e.toString());
- }
- if(cutoutDisp != null)
- {
- Log.i(TAG_CUTOUT,"命中挖孔屏,cutout:"+cutoutDisp.toString());
- }
- }
3.2 设置显示模式
- WindowManager.LayoutParams windowManagerDu = getWindow().getAttributes();
- windowManagerDu.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
- getWindow().setAttributes(windowManagerDu);
3.3 获取状态栏高度
- public static int getStatusBarHeight(Context context) {
- int result = 0;
- int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
- if (resourceId > 0) {
- result = context.getResources().getDimensionPixelSize(resourceId);
- }
- return result;
- }
3.4 调整布局
如果是挖孔屏幕,根据挖孔区域的参数,调整布局避开挖孔区。 布局原则:保证重要的文字、图片、视频信息、可点击的控件和图标,应用弹窗等,建议显示在状态栏区域以下(安全区域)。如果内容不重要或者不会遮挡,布局可以延伸到状态栏区域(危险区域)。
最简陋的方案是根据状态栏的高度,对边界的元素进行整体调整。
二、测试方法
写好了代码,我们需要测试、验证。如果为了测试去专门采购一部挖孔屏手机,成本太高了。其实还有两个办法:
- 使用Android P模拟器。
- 在非挖孔屏真机上开启模拟挖孔屏调试。
真机安卓版本需要大于等于p版本。
在开发人员选项中,向下滚动到绘图部分,然后点击“模拟刘海屏”设置项(有些手机叫做模拟具有凹口的显示屏)。如下图所示:
三、代码总览
- private DisplayCutout cutoutDisp = null;
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
- Log.i(TAG_CUTOUT,"onAttachedToWindow");
- //挖孔屏
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- try{
- WindowInsets windowInsets = getWindow().getDecorView().getRootWindowInsets();
- if (windowInsets != null) {
- cutoutDisp = windowInsets.getDisplayCutout();
- }else{
- Log.i(TAG_CUTOUT, "windowInsets is null");
- }
- }catch (Exception e){
- Log.e(TAG_CUTOUT, "error:"+e.toString());
- }
- if(cutoutDisp != null){
- Log.i(TAG_CUTOUT,"will set mode,cutout:"+cutoutDisp.toString());
- WindowManager.LayoutParams windowManagerDu = getWindow().getAttributes();
- windowManagerDu.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
- getWindow().setAttributes(windowManagerDu);
- AppUtil.CutOutSafeHeight = getStatusBarHeight();
- Log.i(TAG_CUTOUT,"statusBar height:" + AppUtil.CutOutSafeHeight);
- }
- }
- }
- //获取状态栏高度
- public int getStatusBarHeight() {
- Context context = getApplicationContext();
- int result = 0;
- if(cutoutDisp != null){
- int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
- if (resourceId > 0) {
- result = context.getResources().getDimensionPixelSize(resourceId);
- }
- }
- return result;
- }
四、需要注意的几个坑
4.1 DisplayCutout未定义
有些小白,看了文档就上手写代码,却发现无法通过编译,报错undefined symbol DisplayCutout
这是因为编译所用的版本导致的。需要条件:
- 在gradle中设置的compileSdkVersion需要大于等于p版本,可以设置compileSdkVersion 28
- 在代码中导入DisplayCutout
import android.view.DisplayCutout;
扩展阅读:compileSdkVersion、minSdkVersion 和 targetSdkVersion的区别
4.2 在OnCreate方法中设置窗口属性无效
改为在onAttachedToWindow中设置。
不能在onCreate()中修改Window的布局参数。Window的创建以及显示View是在onResume执行后,在WindowManagerService中完成的,然后才会回调Activity的onAttachedToWindow(),此时Window已经显示了附属在其上的View。
onAttachedToWindow在activity中的生命周期为:
onCreate->onStart->onResume->onAttachedToWindow
扩展阅读:onAttachedToWindow()在整个Activity生命周期的位置及使用
4.3 获取getDisplayCutout结果为null
改为在onAttachedToWindow中获取。
扩展阅读:https://stackoverflow.com/questions/53575066/null-window-insets
2020年8月5日 下午5:17 沙发
AppUtil.CutOutSafeHeight
AppUtil这个是什么类型的啊QAQ 指哪个
2020年8月6日 上午9:55 1层
@点点 是个辅助类,这个看业务运用的需要哈,和核心功能无关。它长这样:
public class AppUtil {
public static int CutOutSafeHeight = 0;
}