V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
cgspine
V2EX  ›  Android

QMUI 刘海屏适配方案

  •  
  •   cgspine · 2018-09-14 15:23:49 +08:00 · 8571 次点击
    这是一个创建于 2261 天前的主题,其中的信息可能已经有所发展或是发生改变。

    自 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。

    目前尚无回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3795 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 17ms · UTC 10:15 · PVG 18:15 · LAX 02:15 · JFK 05:15
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.