68

QMUI 刘海屏适配方案

 5 years ago
source link: http://blog.cgsdream.org/2018/08/10/android-notch/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

QMUI 新版本就添加了 QMUINotchHelper 以及相关组件,这篇文章就是简要介绍 QMUI 的封装方案以及相关使用要点。

如果使用了 QMUI 的沉浸式方案,刘海屏就只有在有全屏场景的 App 需要做特殊兼容

引入QMUI

implementation "com.qmuiteam:qmui:1.1.6"

兼容平台

  • 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" />

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 等手机提供的所有设置项,更不能兼容到某些厂商白名单带来的不同效果

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。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK