无需再怨恨“刘海屏”了,因为适配十分简单
网上关于刘海屏适配的文章不少,可讲清楚的却没几篇,大多是拷贝文档、长篇大论,甚至热情的贴图告诉你什么是刘海屏,到最后你仍不确定到底是怎样的一个适配方案,才能让你的 app 真正的适配所有的刘海屏机型。
看到这篇文章你就无需再怨恨各大厂商的跟风“刘海”了,因为刘海屏的适配十分简单。
ok,废话说完了,开始适配。
首先要清楚的是哪些界面需要适配刘海屏:
- 有状态栏的界面:刘海区域会显示状态栏,无需适配
- 全屏界面:刘海区域可能遮挡内容,需要适配
如果你的应用里所有界面都有状态栏,那么恭喜你,你不用做任何操作,状态栏就那么自然的显示在刘海区域,毫无违和,刘海屏已适配完毕,可以点叉出去了。
不幸的是,你的应用中很大几率会有全屏界面,所谓的刘海屏适配,也正是针对这些全屏界面。
如果你什么都不做,默认规则不允许全屏界面内容显示到刘海区域,即刘海屏区域会保留一条黑边,你的全屏界面会在刘海下方展示,这看起来好像也是可以接受的,然后你竟说服产品达成共识,“无为而治”才是最强大的刘海屏适配方案!
但有些手机厂商(譬如oppo)不开心了,我辛辛苦苦研发的刘海屏手机,你们这些开发者竟直接放弃刘海区域!然后就在你的全屏界面下方加了一条提示:“全屏显示”,当用户点击开启后,强行把你的全屏界面显示到刘海区域,然后一切都乱套了...
嗯~ “无为而治”行不通。
只能允许全屏界面内容显示到刘海区域了,参考各大厂商的适配文档,我们可以知道如何允许,比如华为机型只需在 AndroidManifest 中配置:
[code] <meta-data android:name="android.notch_support" android:value="true" />
配置后,华为机型上的全屏界面就会显示到刘海区域了,但这个刘海,是可能挡住我们全屏界面中的内容的。这时需要将全屏界面中的视图元素适当下移,保证不会被刘海遮挡住,就 ok 了。
这里我们搞清楚:允许全屏界面内容显示到刘海区域的机型,才需要将全屏界面中的视图元素适当下移。
比如若只允许华为机型全屏界面内容显示到刘海区域,那只有华为的刘海屏机型才需要将全屏界面中的视图元素适当下移,其他厂商的刘海屏机型则不需要下移。
如果允许华为、小米、oppo、vivo 全屏界面内容显示到刘海区域,那么华为、小米、oppo、vivo 刘海屏机型需要将全屏界面中的视图元素适当下移。
另外也不一定要通过全屏界面中的视图元素适当下移方式来适配刘海屏,如果产品形态允许的话,你也可以让该界面显示状态栏啊。
至此刘海屏适配完毕,是不是很简单!?
最后代码奉上,拿走不谢:
允许全屏界面内容显示到刘海区域配置:
[code] <!--允许绘制到oppo、vivo刘海屏机型的刘海区域 --> <meta-data android:name="android.max_aspect" android:value="2.2" /> <!-- 允许绘制到华为刘海屏机型的刘海区域 --> <meta-data android:name="android.notch_support" android:value="true" /> <!-- 允许绘制到小米刘海屏机型的刘海区域 --> <meta-data android:name="notch.config" android:value="portrait" />
上面在 AndroidManifest 的配置在 Android 9.0 之前有效,9.0 系统针对刘海屏制定了新的 api,默认保留一条黑边,即不允许绘制到刘海区域。所以如果你还没有适配 Android 9.0,那在判断是否是允许全屏界面内容显示到刘海区域的刘海屏机型时,就要加上版本判断。
判断是否是允许全屏界面内容显示到刘海区域的刘海屏机型:
[code]public class CutoutUtil { private static Boolean sAllowDisplayToCutout; /** * 是否为允许全屏界面显示内容到刘海区域的刘海屏机型(与AndroidManifest中配置对应) */ public static boolean allowDisplayToCutout() { if (sAllowDisplayToCutout == null) { if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O_MR1) { // 9.0系统全屏界面默认会保留黑边,不允许显示内容到刘海区域 return sAllowDisplayToCutoutDevice = false; } Context context = App.get(); if (hasCutout_Huawei(context)) { return sAllowDisplayToCutout = true; } if (hasCutout_OPPO(context)) { return sAllowDisplayToCutout = true; } if (hasCutout_VIVO(context)) { return sAllowDisplayToCutout = true; } if (hasCutout_XIAOMI(context)) { return sAllowDisplayToCutout = true; } return sAllowDisplayToCutout = false; } else { return sAllowDisplayToCutout; } } /** * 是否是华为刘海屏机型 */ @SuppressWarnings("unchecked") private static boolean hasCutout_Huawei(Context context) { if (!Build.MANUFACTURER.equalsIgnoreCase("HUAWEI")) { return false; } try { ClassLoader cl = context.getClassLoader(); Class HwNotchSizeUtil = cl.loadClass("com.huawei.android.util.HwNotchSizeUtil"); if (HwNotchSizeUtil != null) { Method get = HwNotchSizeUtil.getMethod("hasNotchInScreen"); return (boolean) get.invoke(HwNotchSizeUtil); } return false; } catch (Exception e) { return false; } } /** * 是否是oppo刘海屏机型 */ @SuppressWarnings("unchecked") private static boolean hasCutout_OPPO(Context context) { if (!Build.MANUFACTURER.equalsIgnoreCase("oppo")) { return false; } return context.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism"); } /** * 是否是vivo刘海屏机型 */ @SuppressWarnings("unchecked") private static boolean hasCutout_VIVO(Context context) { if (!Build.MANUFACTURER.equalsIgnoreCase("vivo")) { return false; } try { ClassLoader cl = context.getClassLoader(); @SuppressLint("PrivateApi") Class ftFeatureUtil = cl.loadClass("android.util.FtFeature"); if (ftFeatureUtil != null) { Method get = ftFeatureUtil.getMethod("isFeatureSupport", int.class); return (boolean) get.invoke(ftFeatureUtil, 0x00000020); } return false; } catch (Exception e) { return false; } } /** * 是否是小米刘海屏机型 */ @SuppressWarnings("unchecked") private static boolean hasCutout_XIAOMI(Context context) { if (!Build.MANUFACTURER.equalsIgnoreCase("xiaomi")) { return false; } try { ClassLoader cl = context.getClassLoader(); @SuppressLint("PrivateApi") Class SystemProperties = cl.loadClass("android.os.SystemProperties"); Class[] paramTypes = new Class[2]; paramTypes[0] = String.class; paramTypes[1] = int.class; Method getInt = SystemProperties.getMethod("getInt", paramTypes); //参数 Object[] params = new Object[2]; params[0] = "ro.miui.notch"; params[1] = 0; return (Integer) getInt.invoke(SystemProperties, params) == 1; } catch (Exception e) { return false; } } }
上面提到,不一定要通过全屏界面中的视图元素适当下移方式来适配刘海屏,如果产品形态允许的话,也可以让该界面显示状态栏。
显示状态栏的方案是较为通用简单的,或者说,在一个应用中,一些全屏界面往往是允许使用显示状态栏的方案来适配的,如果你考虑使用这种方案,那便会是这种效果:
- 在你的应用中,你期望某些全屏界面在刘海屏机型上必须全屏展示,那你就自行将界面元素适当下移,从而避免被刘海遮挡;而某些全屏界面不是非要全屏显示,允许在刘海屏机型显示状态栏,那就通过显示状态栏的方式,从而避免被刘海遮挡。
为了实现这种效果,我们需要标记区分哪些界面必须全屏展示、哪些界面允许显示状态栏。这里提供一种实现方式,让允许显示状态栏的界面 Activity 继承一个接口,比如:
[code]public interface CutoutAdapt { }
然后在 ActivityLifecycleCallbacks 回调,统一适配允许通过显示状态栏的全屏界面:
[code] @Override public void onActivityStarted(Activity activity) { // 如果是允许全屏显示到刘海屏区域的刘海屏机型 if (CutoutUtil.allowDisplayToCutout()) { if (isFullScreen(activity)) { // 如果允许通过显示状态栏方式适配刘海屏 if (activity instanceof CutoutAdapt) { // 显示状态栏 StatusBarUtil.showStatusbar(activity.getWindow()); } else { // 需自行将该界面视图元素下移,否则可能会被刘海遮挡 } } else { // 非全屏界面无需适配刘海屏 } } }
【附】相关架构及资料
资料领取
关注+点赞+加群:791358629 免费获取!
点击链接加入群聊【Android 架构设计③群】:https://jq.qq.com/?_wv=1027&k=5N9wWae
领取获取往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术
- 一个简单易用的低功耗蓝牙框架。兼容性好、适配到 6.0、适配小米三星手机、操作简单、支持连发无需延时、自带队列管理、支持多通知
- 爱情:因为简单,所以没负担
- 十分简单的redis使用说明及性能测试
- Android屏幕适配简单方案
- 最简单的屏幕适配
- Android高级控件(四)——VideoView 实现引导页播放视频欢迎效果,超级简单却十分的炫酷
- JS简单实现文件上传实例代码(无需插件)
- 沉浸式状态栏,终结版,简单方面,适配多种机型,直接调用
- 十分简单-如何在局域网中将Ubuntu文件夹共享给Windows
- android简单可操作的屏幕适配
- QTableWidget使用简单,因为不再存在父节点的关系
- vb做的类似打鼹鼠的游戏,简单,十分推荐初学vb 想做游戏的人学习
- 可视化编程 Tips 之"无需代码设置圆角" "简单处理让我们可以无需代码设置边框颜色"
- ASP.net实现无扩展名的URL重写。简单、方便、无需ISAPI。
- 【云盒子企业网盘】无需等待,简单几步项目跟进轻松搞定!
- php导出excel最简单的办法,无需phpexcel
- [原] 发个简单的代码,因为很多人 分不清char c[] 和 char *c
- android:使用SurfaceView游戏开发简单屏幕适配方法
- 屏幕适配的简单介绍
- C#建立最简单的web服务,无需IIS