螢幕凹口

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(),檢視畫面就能判斷裁剪區域的位置,並據此更新版面配置。

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

這些方法會概略說明狀態列中在任何情況下 (即所有旋轉角度的上方中心、未置中、底部和雙切線) 處理剪裁的方式。

需求條件

為確保應用程式不會受到裁剪的負面影響,請務必確保以下事項:

  • 在直向模式下,狀態列至少會延伸到螢幕缺口的高度
  • 在全螢幕和橫向模式下,裁剪區域必須加上黑邊

裝置的每條短邊 (頂部和底部) 最多可以有一個凹口。

詳情請參閱 CDD

實作

如要在裝置上實作螢幕凹口,請為系統 UI 設定下列值。

說明
quick_qs_offset_height

定義快速設定面板的上邊界。時鐘和電池會顯示在面板上方的空間中。

在 values-land 中,將其設為 status_bar_height_landscape,在直向模式中,將其設為預設的 48 dp,或 cutout 的高度 (以較大者為準)。視需要可高於裁剪區。

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 指定符。

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 測試。