刘海屏

Android 9 新增了对在设备上实现不同类型刘海屏的支持。通过刘海屏,您可以打造沉浸式全面屏体验,同时继续在设备前端为重要传感器留出空间。

顶部中间刘海屏

图 1. 顶部中间刘海屏

Android 9 支持以下类型的刘海屏:

  • 顶部中间刘海屏:刘海屏位于顶部边缘的中间位置
  • 顶部非中间刘海屏:刘海屏位于边角处或稍微偏离中心的位置
  • 底部刘海屏:刘海屏位于底部
  • 双刘海屏:一个刘海屏位于顶部,一个位于底部

示例和源代码

以下窗口管理器代码 (PhoneWindowManager.java) 显示了如何在未设置 LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS 的情况下将显示帧嵌入安全区域。

// Ensure that windows with a DEFAULT or NEVER display cutout mode are laid out in
// the cutout safe zone.
if (cutoutMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) {
    final Rect displayCutoutSafeExceptMaybeBars = mTmpDisplayCutoutSafeExceptMaybeBarsRect;
    displayCutoutSafeExceptMaybeBars.set(displayFrames.mDisplayCutoutSafe);
    if (layoutInScreen && layoutInsetDecor && !requestedFullscreen
            && cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT) {
        // At the top we have the status bar, so apps that are
        // LAYOUT_IN_SCREEN | LAYOUT_INSET_DECOR but not FULLSCREEN
        // already expect that there's an inset there and we don't need to exclude
        // the window from that area.
        displayCutoutSafeExceptMaybeBars.top = Integer.MIN_VALUE;
    }
    if (layoutInScreen && layoutInsetDecor && !requestedHideNavigation
            && cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT) {
        // Same for the navigation bar.
        switch (mNavigationBarPosition) {
            case NAV_BAR_BOTTOM:
                displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE;
                break;
            case NAV_BAR_RIGHT:
                displayCutoutSafeExceptMaybeBars.right = Integer.MAX_VALUE;
                break;
            case NAV_BAR_LEFT:
                displayCutoutSafeExceptMaybeBars.left = Integer.MIN_VALUE;
                break;
        }
    }
    if (type == TYPE_INPUT_METHOD && mNavigationBarPosition == NAV_BAR_BOTTOM) {
        // The IME can always extend under the bottom cutout if the navbar is there.
        displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE;
    }
    // Windows that are attached to a parent and laid out in said parent already avoid
    // the cutout according to that parent and don't need to be further constrained.
    // Floating IN_SCREEN windows get what they ask for and lay out in the full screen.
    // They will later be cropped or shifted using the displayFrame in WindowState,
    // which prevents overlap with the DisplayCutout.
    if (!attachedInParent && !floatingInScreenWindow) {
        mTmpRect.set(pf);
        pf.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
        parentFrameWasClippedByDisplayCutout |= !mTmpRect.equals(pf);
    }
    // Make sure that NO_LIMITS windows clipped to the display don't extend under the
    // cutout.
    df.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
}

SystemUI 在刘海屏区域呈现,且需要确定可以绘制的位置。PhoneStatusBarView.java 提供了一个视图示例,它确定了刘海屏的位置、刘海屏大小,以及从导航栏嵌入是否可以避开刘海屏区域。

通过覆盖 onApplyWindowInsets()onApplyWindowInsets(),视图可以确定刘海屏的位置,并相应地更新其布局。

@Override
    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
        if (updateOrientationAndCutout(mLastOrientation)) {
            updateLayoutForCutout();
            requestLayout();
        }
        return super.onApplyWindowInsets(insets);
    }

这些方法概述了各种类型的刘海屏(即顶部中间刘海屏、顶部非中间刘海屏、底部刘海屏和双刘海屏)在状态栏中的处理方式。

要求

要确保刘海屏不会对应用造成负面影响,您必须确保:

  • 在竖屏模式下,状态栏的高度至少与刘海屏的高度持平
  • 在全屏模式和横屏模式下,刘海屏区域必须显示遮幅式黑边

您的设备最多可以在每个短边处(顶部和底部)各设一个刘海屏。

如需了解详情,请参阅 CDD

实现

如需在设备上实现刘海屏,您必须为系统界面配置以下值。

说明
quick_qs_offset_height

定义“快捷设置”面板的上外边距。时钟和电池图标显示在该面板上方的空间。

在 values-land 中,设置为 status_bar_height_landscape,在纵屏中,将此值设置为默认值 48dp 或刘海屏高度(以较大者为准)。可以根据需要选择高于刘海屏的高度。

quick_qs_total_height

展开通知栏时,“超快设置”面板(收起的“快捷设置”面板)的总高度(其中包括包含时钟图标的面板上方的空间)。

由于快捷设置的布局方式,“超快设置”面板(包括偏移量)的总高度必须是静态已知的,因此该值必须使用相同的增量 quick_qs_offset_height。在 Values-land 中,此值默认为 152dp,在纵屏模式下,此值默认为 176dp。

status_bar_height_portrait

从框架的角度而言,状态栏的默认高度。

在大多数设备上,此值默认为 24dp。如果设备上有刘海屏,则将此值设置为刘海屏的高度。可以根据需要选择高于刘海屏的高度。

status_bar_height_landscape

状态栏在横屏模式下的高度。由于仅支持在设备的短边上显示刘海屏,因此此值始终是未经更改的状态栏高度。

在没有刘海屏的设备上,此值等同于 status_bar_height_portrait。如果设备上有刘海屏,则将此值保留为默认的状态栏高度。

config_mainBuiltInDisplayCutout

用于定义刘海屏形状的路径。这是可由 android.util.PathParser 解析的字符串,这也是系统如何定义刘海的大小和形状。

可在路径中指定 @dp@dp 以便模拟针对不同设备的形状。由于实际的刘海屏具有精确的像素尺寸,因此在定义硬件刘海屏的路径时,请勿使用 @dp@dp 指定符。

config_fillMainBuiltinDisplayCutout

一个确定是否在软件中绘制刘海屏路径(在上文中进行了定义)的布尔值。可用于模拟刘海屏,或填充实际刘海屏,以实现抗锯齿。

如果为 true,则系统会以黑色填充 config_mainBuiltInDisplayCutout

如需了解默认定义,请参阅以下 dimens 文件:

模拟刘海屏的示例叠加层:

<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">

    <!-- The bounding path of the cutout region of the main built-in display.
         Must either be empty if there is no cutout region, or a string that is parsable by
         {@link android.util.PathParser}.

         The path is assumed to be specified in display coordinates with pixel units and in
         the display's native orientation, with the origin of the coordinate system at the
         center top of the display.

         To facilitate writing device-independent emulation overlays, the marker `@dp` can be
         appended after the path string to interpret coordinates in dp instead of px units.
         Note that a physical cutout should be configured in pixels for the best results.
         -->
    <string translatable="false" name="config_mainBuiltInDisplayCutout">
        M 0,0
        L -48, 0
        L -44.3940446283, 36.0595537175
        C -43.5582133885, 44.4178661152 -39.6, 48.0 -31.2, 48.0
        L 31.2, 48.0
        C 39.6, 48.0 43.5582133885, 44.4178661152 44.3940446283, 36.0595537175
        L 48, 0
        Z
        @dp
    </string>

    <!-- Whether the display cutout region of the main built-in display should be forced to
         black in software (to avoid aliasing or emulate a cutout that is not physically existent).
     -->
    <bool name="config_fillMainBuiltInDisplayCutout">true</bool>

    <!-- Height of the status bar -->
    <dimen name="status_bar_height_portrait">48dp</dimen>
    <dimen name="status_bar_height_landscape">28dp</dimen>
    <!-- Height of area above QQS where battery/time go (equal to status bar height if > 48dp) -->
    <dimen name="quick_qs_offset_height">48dp</dimen>
    <!-- Total height of QQS (quick_qs_offset_height + 128) -->
    <dimen name="quick_qs_total_height">176dp</dimen>

</resources>

验证

如需验证刘海屏的实现,请在以下位置运行 CTS 测试:tests/framework/base/windowmanager/src/android/server/wm