QMUI 刘海屏适配方案 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
cgspine

QMUI 刘海屏适配方案

  •  
  •   cgspine Sep 14, 2018 12633 views
    This topic created in 2781 days ago, the information mentioned may be changed or developed.

    自 iPhone X 出了个刘海屏后,Android 各大厂商就先后跟进。由于 Android 碎片化严重,各大厂商各自为政,导致 Android 刘海屏的适配可谓痛苦,而网上的适配文章基本上只是简单的对官方文档做了一次搬运,对于业务线的同学来说,太不好使用了,因而我们需要做一次封装,解决各种兼容问题,让业务线最小程度感知刘海屏的存在。

    QMUI 新版本就添加了 QMUINotchHelper 以及相关组件,这篇文章就是简要介绍 QMUI 的封装方案以及相关使用要点,可以从官网下载 apk,点击 helper -> QMUINotchHelper 体验效果

    如果使用了 QMUI 的沉浸式方案,非全屏场景都会由沉浸式方案自动适配好,因此 QMUINotchHelper 只是针对全屏场景做的特殊兼容。

    引入 QMUI

    implementation "com.qmuiteam:qmui:1.1.7" 

    兼容平台

    • Android P+(官方)
    • 小米
    • 华为
    • Vivo
    • Oppo
    • Essential Phone

    AndroidManifest 设置

    <meta-data android:name="android.max_aspect" android:value="2.34" /> <!-- huawei --> <meta-data android:name="android.notch_support" android:value="true" /> <!-- xiaomi --> <meta-data android:name="notch.config" android:value="portrait|landscape"/> 

    QMUINotchHelper

    QMUI 的接口参考 Android P 官方接口,提供了如下主要几个接口:

    // 是否有刘海屏 QMUINotchHelper.hasNotch(Activity | View) // 左边的安全距离 QMUINotchHelper.getSafeInsetLeft(Activity | View) // 上边的安全距离 QMUINotchHelper.getSafeInsetTop(Activity | View) // 右边的安全距离 QMUINotchHelper.getSafeInsetRight(Activity | View) // 下边的安全距离 QMUINotchHelper.getSafeInsetBottom(Activity | View) 

    或许有人觉得奇怪:为何传参都是 Activity 或者 View, 而不是 Context?这我们需要知道 Android P 是如何去适配刘海屏的:Android P 提供了 DisplayCutout 类, 那么如何获取 DisplayCutout的实例呢 ?有两种方式:

    1. 在 View 中重写 onApplyWindowInsets(或者使用 setOnApplyWindowInsetsListener), 通过 windowInset.getDisplayCutout() 来获取;
    2. 当 View 已经 attach 到 window 上时, 通过 view.getRootWindowInsets().getDisplayCutout()

    第一种方式获取到的值在全屏和非全屏下是不一样的。非全屏下,得到的值为 null, 如果我们的 App 需要动态切换全屏与非全屏,我们获取的可布局区域不一样,这很容易造成界面跳动,因此不可取。 第二种方案, 很少有人或文档提及,但却非常准确,因此 QMUI 里面基本上都是依靠方式 2 来完成 Android P 的适配的。当然,如果 view 没有 attach 到 window 上, 那么就得不到 rootWindowInsets 信息, 因此这是一个坑点:

    坑点 1:通过 QMUINotchHelper 获取刘海屏信息并传参为 View 时,View 必须是已经 attach 到 window 上了的。

    获取屏幕可用宽高信息

    除了 QMUINotchHelper 外, QMUIDisplayHelper 添加了两个重要方法:

    // 获取屏幕可用宽度 QMUIDisplayHelper.getUsefulScreenWidth(Activity | View) // 获取屏幕可用高度 QMUIDisplayHelper.getUsefulScreenHeight(Activity | View) 

    为何需要这几个方法?因为华为、Vivo、Oppo、小米这国内四巨头在设置里都有诸如是否使用刘海区域的设置项。如果不使用,那么就会把整个 window 进行偏移,所以 getRealScreenSize 并不能代表可以使用的区域,所以在 QMUI 里增加这两个方法,帮助开发者处理掉不能使用的区域。 因此,在 QMUI 上,获取屏幕宽高息的就有三套了: getScreenSizegetUsefulScreengetRealScreenSize。 (使用者更加蛋疼了,可能连 getScreenSizegetRealScreenSize 的区别都不知道...)

    提供了这两个方法,但是其实并不好用,因为并不是特别准确,不准确的原因就是 Vivo、Oppo 等手机添加了设置项而不提供接口(连文档都不说一下,只有踩坑后才知道...),让我们更列举下:

    • Vivo 设置-系统导航-导航手势样式-显示手势操作区域 打开的情况下,应该减去手势操作区域的值,但无判断 API。
    • Vivo 设置-显示与亮度-第三方应用显示比例 选为安全区域显示时,整个 window 会移动,应该减去移动区域的值,但无判断 API。
    • Oppo 设置-显示与亮度-应用全屏显示-凹形区域显示控制 关闭是,整个 window 会移动,应该减去移动区域,但无判断 API。
    • Essential Phone 升级到 Android 8 后,在开发者选项中也提供了设置项,但也没有相关 API。 此外 Essential Phone 的 getRealScreenSize 也会随着全屏的取消与显示而有不同的值,这等价于getUsefulScreen 的效果。

    如果能够找到相应的 API, 那么这些方法也是可以逐步变得准确的,而目前而言,我也无话可说。

    坑点 2:QMUI 的刘海屏并不能兼容到 Vivo、Oppo 等手机提供的所有设置项,更不能兼容到某些厂商白名单带来的不同效果

    坑点 3:小米 8 在横屏状态下 WindowInsets 的左右值会出错,导致 fitSystemWindows 失效。此外,旋转屏幕,小米也不会重下发 windowInsets

    QMUINotchConsumeLayout

    绝大多数场景,我们需要的是 View 最外层容器消耗掉 Notch 带来的不安全区域,所以我提供了一个简单的容器类:QMUINotchConsumeLayout, 其需要配合 QMUIWindowInsetLayout 等实现了 IWindowInsetLayout 的容器类来使用, 例如 QMUIDemo 给的使用案例:

    <com.qmuiteam.qmui.widget.QMUIWindowInsetLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/qmui_config_color_white"> <com.qmuiteam.qmui.widget.QMUINotchConsumeLayout android:id="@+id/not_safe_bg" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- 具体内容 --> </com.qmuiteam.qmui.widget.QMUINotchConsumeLayout> </com.qmuiteam.qmui.widget.QMUIWindowInsetLayout> 

    如果 QMUINotchConsumeLayout 无法满足需求, 可以参考QMUINotchConsumeLayout 在 View 层级里灵活处理:

    首先,需要实现 INotchInsetConsumer 来接收 Android P+ 上 Notch 信息 的派发,这个接口提供了一个方法:

    // 返回 true 时,停止向子 View 派发 Notch 信息 boolean notifyInsetMaybeChanged(); 

    如果是第三方厂商实现,需要在 onAttachedToWindowonConfigurationChanged 处理,处理方式也很简单,通过 padding 消耗掉不安全区域:

    setPadding( QMUINotchHelper.getSafeInsetLeft(this), QMUINotchHelper.getSafeInsetTop(this), QMUINotchHelper.getSafeInsetRight(this), QMUINotchHelper.getSafeInsetBottom(this) ); 

    基本上就是这么多。当然,各大厂商的 API 也是朝令夕改, 也不知道升级到 Android P 后会不会遵循官方的方案,因此刘海屏的适配也只能走一步看一步。测试机型也很有限,如果发现不完善的地方或者未适配的机型,欢迎提 issue。

    No Comments Yet
    About     Help     Advertise     Blog     API     FAQ     Solana     4447 Online   Highest 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 116ms UTC 04:09 PVG 12:09 LAX 21:09 JFK 00:09
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86