디스플레이 컷아웃

Android 9에는 기기에 다양한 유형의 디스플레이 컷아웃을 구현할 수 있도록 하기 위한 지원이 추가되었습니다. 디스플레이 컷아웃을 사용하면 더 넓고 몰입감 있는 환경을 구축하는 동시에 계속해서 기기 전면의 중요한 센서를 위한 공간을 확보할 수 있습니다.

상단 중앙 디스플레이 컷아웃

그림 1. 상단 중앙 디스플레이 컷아웃

Android 9는 다음과 같은 컷아웃 유형을 지원합니다.

  • 상단 중앙: 상단 가장자리 중앙의 컷아웃
  • 상단 비중앙: 컷아웃이 모서리에 위치하거나 중앙에서 약간 벗어날 수 있음
  • 하단: 하단의 컷아웃
  • 이중: 상단의 컷아웃 1개와 하단의 컷아웃 1개

예시 및 소스

아래에 나온 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는 디스플레이 컷아웃 위치, 크기, navbar의 인셋이 컷아웃 영역을 피하는지를 결정하는 보기의 예를 제공합니다.

보기는 onApplyWindowInsets()를 재정의하여 컷아웃의 위치를 결정하고 이에 맞게 레이아웃을 업데이트할 수 있습니다.

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

이러한 방식은 컷아웃이 모든 사례의 상태 창에서 처리되는 방식에 대한 개요를 보여줍니다(상단 중앙, 상단 비중앙, 하단 및 모든 회전의 이중 컷아웃).

요구사항

앱에 컷아웃에 의한 부정적인 영향이 미치지 않도록 하려면 다음을 확인해야 합니다.

  • 상태 표시줄은 최소한 세로 모드의 컷아웃 높이까지는 연장되어야 합니다.
  • 컷아웃 영역이 전체화면 및 가로 모드에서 레터박스 처리되어야 합니다.

기기 상단 및 하단의 각 모퉁이에 한 개의 컷아웃이 있어야 합니다.

자세한 내용은 CDD를 참조하세요.

구현

기기에 디스플레이 컷아웃을 구현하려면 시스템 UI에 다음과 같은 값을 구성해야 합니다.

설명
quick_qs_offset_height

빠른 설정 패널의 상단 여백을 정의합니다. 시계와 배터리는 패널 위의 공간에 표시됩니다.

값 랜딩에서는 status_bar_height_landscape로 설정하고 세로 모드에서는 기본값 48dp 또는 컷아웃의 높이 중 큰 값으로 설정합니다. 원하는 경우 컷아웃보다 높게 설정할 수 있습니다.

quick_qs_total_height

알림 창이 확장되었을 때의 빠른-빠른 설정 패널의 총 높이이며(축소된 빠른 설정 패널), 시계를 포함하는 패널 위의 공간을 포함합니다.

빠른 설정이 표시되는 방식 때문에 빠른-빠른 설정 패널 (오프셋 포함)의 총 높이는 정적인 상태에서 파악해야 합니다. 따라서 이 값은 같은 델타 quick_qs_offset_height로 조정되어야 합니다). 값 랜딩은 이 값을 152dp로 기본 설정하며 세로 모드의 기본값은 176dp입니다.

status_bar_height_portrait

프레임워크 시점에서 본 상태 표시줄의 기본 높이입니다.

대부분의 기기에서는 이 값이 24dp로 기본 설정됩니다. 컷아웃이 존재하는 경우 이 값을 컷아웃 높이로 설정합니다. 원하는 경우 컷아웃보다 높게 설정할 수 있습니다.

status_bar_height_landscape

가로 모드에서의 상태 표시줄 높이입니다. 컷아웃은 기기의 모퉁이에서만 지원되므로 항상 변경되지 않은 상태 표시줄 높이를 유지합니다.

컷아웃이 없는 기기에서는 status_bar_height_portrait와 동일합니다. 컷아웃이 있는 경우 이 값을 기본 상태 표시줄 높이로 유지합니다.

config_mainBuiltInDisplayCutout

컷아웃 모양을 정의하는 경로입니다. 이는 android.util.PathParser에서 파싱할 수 있는 문자열이며 컷아웃의 크기와 모양이 시스템에 정의되는 방식입니다.

@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>

유효성 검사

디스플레이 컷아웃의 구현이 유효한지 검사하려면 tests/framework/base/windowmanager/src/android/server/wm에서 CTS 테스트를 실행하세요.