關於 WindowInsetsRulers

WindowInsets 是 Jetpack Compose 中的標準 API,用於處理部分或完全遭系統 UI 遮蔽的螢幕區域。這些區域包括狀態列、導覽列和螢幕小鍵盤。您也可以傳遞預先定義的 WindowInsetsRulers (例如 SafeDrawing),將內容與系統資訊列和螢幕凹口對齊,或建立自訂 WindowInsetsRulersModifier.fitInsideModifier.fitOutside

WindowInsetsRulers 的優點

  • 避免耗用複雜度:在版面配置的放置階段運作。也就是說,無論上層版面配置執行了什麼操作,它都會完全略過插邊消耗鏈,並一律提供系統資訊列和螢幕凹口的正確絕對位置。如果上層可組合項錯誤地消耗插邊,使用 Modifier.fitInsideModifier.fitOutside 方法有助於修正問題。
  • 輕鬆避開系統資訊列:有助於應用程式內容避開系統資訊列和螢幕凹口,比使用 WindowInsets 更直接。
  • 高度可自訂:開發人員可將內容對齊自訂尺規,並透過自訂版面配置精確控管版面配置。

WindowInsetsRulers 的缺點

  • 無法用於評估:由於這項功能是在刊登位置階段運作,因此在較早的評估階段無法取得其提供的位置資訊。

使用修飾符方法對齊內容

Modifier.fitInside 可讓應用程式將內容對齊系統資訊列和螢幕凹口。可取代 WindowInsetsModifier.fitOutside 通常是 Modifier.fitInside 的反向。

舉例來說,如要確認應用程式內容是否避開系統資訊列和螢幕凹口,可以使用 fitInside(WindowInsetsRulers.safeDrawing.current)

@Composable
fun FitInsideDemo(modifier: Modifier) {
    Box(
        modifier = modifier
            .fillMaxSize()
            // Or DisplayCutout, Ime, NavigationBars, StatusBar, etc...
            .fitInside(WindowInsetsRulers.SafeDrawing.current)
    )
}

下表顯示使用預先定義的尺規時,應用程式內容在 Modifier.fitInsideModifier.fitOutside 上的顯示方式。

預先定義的尺規類型

Modifier.fitInside

Modifier.fitOutside

DisplayCutout

Ime

不適用

NavigationBars

SafeDrawing

不適用 (請改用 StatusBarCaptionBarNavigationBar)

StatusBar

使用 Modifier.fitInsideModifier.fitOutside 時,可組合項必須受到限制。也就是說,您必須定義 Modifier.sizeModifier.fillMaxSize 等修飾符。

Modifier.fitOutsideSafeDrawingSystemBars 等部分尺規會傳回多個尺規。在這種情況下,Android 會使用左、上、右、下其中一個尺規放置 Composable。

避免使用 IME 和 Modifier.fitInside

如要使用 IME 處理具有 Modifier.fitInside 的底部元素,請傳入 RectRuler,該元素會採用 NavigationBarIme 的最內層值。

@Composable
fun FitInsideWithImeDemo(modifier: Modifier) {
    Box(
        modifier = modifier
            .fillMaxSize()
            .fitInside(
                RectRulers.innermostOf(
                    WindowInsetsRulers.NavigationBars.current,
                    WindowInsetsRulers.Ime.current
                )
            )
    ) {
        TextField(
            value = "Demo IME Insets",
            onValueChange = {},
            modifier = modifier.align(Alignment.BottomStart).fillMaxWidth()
        )
    }
}

使用 Modifier.fitInside 避開狀態列和說明文字列

同樣地,如要驗證頂端元素是否同時避開狀態列和說明文字列,請傳遞 Modifier.fitInsider,並採用 StatusBarsCaptionBar 的最內層值。RectRuler

@Composable
fun FitInsideWithStatusAndCaptionBarDemo(modifier: Modifier) {
    Box(
        modifier = modifier
            .fillMaxSize()
            .fitInside(
                RectRulers.innermostOf(
                    WindowInsetsRulers.StatusBars.current,
                    WindowInsetsRulers.CaptionBar.current
                )
            )
    )
}

建立自訂 WindowInsetsRulers

你可以將內容對齊自訂尺規。舉例來說,假設父項可組合函式不當處理插邊,導致下游子項發生邊框間距問題。雖然這個問題可以透過其他方式解決,包括使用 Modifier.fitInside,但您也可以建立自訂尺規,精確對齊子項可組合函式,而不必修正上游父項中的問題,如下列範例和影片所示:

@Composable
fun WindowInsetsRulersDemo(modifier: Modifier) {
    Box(
        contentAlignment = BottomCenter,
        modifier = modifier
            .fillMaxSize()
            // The mistake that causes issues downstream, as .padding doesn't consume insets.
            // While it's correct to instead use .windowInsetsPadding(WindowInsets.navigationBars),
            // assume it's difficult to identify this issue to see how WindowInsetsRulers can help.
            .padding(WindowInsets.navigationBars.asPaddingValues())
    ) {
        TextField(
            value = "Demo IME Insets",
            onValueChange = {},
            modifier = modifier
                // Use alignToSafeDrawing() instead of .imePadding() to precisely place this child
                // Composable without having to fix the parent upstream.
                .alignToSafeDrawing()

            // .imePadding()
            // .fillMaxWidth()
        )
    }
}

fun Modifier.alignToSafeDrawing(): Modifier {
    return layout { measurable, constraints ->
        if (constraints.hasBoundedWidth && constraints.hasBoundedHeight) {
            val placeable = measurable.measure(constraints)
            val width = placeable.width
            val height = placeable.height
            layout(width, height) {
                val bottom = WindowInsetsRulers.SafeDrawing.current.bottom
                    .current(0f).roundToInt() - height
                val right = WindowInsetsRulers.SafeDrawing.current.right
                    .current(0f).roundToInt()
                val left = WindowInsetsRulers.SafeDrawing.current.left
                    .current(0f).roundToInt()
                measurable.measure(Constraints.fixed(right - left, height))
                    .place(left, bottom)
            }
        } else {
            val placeable = measurable.measure(constraints)
            layout(placeable.width, placeable.height) {
                placeable.place(0, 0)
            }
        }
    }
}

以下影片顯示有問題的 IME 插入內容耗用情形示例,左側圖片顯示上游父項導致的問題,右側圖片則顯示如何使用自訂尺規修正問題。TextField 可組合項下方會顯示額外邊框間距,因為父項未耗用導覽列邊框間距。如先前程式碼範例所示,孩子會使用自訂尺規放置在右側圖片的正確位置。

確認家長受到限制

如要安全使用 WindowInsetsRulers,請確認家長提供有效限制。父項必須有定義的大小,且不能取決於使用 WindowInsetsRulers 的子項大小。在父項可組合函式上使用 fillMaxSize 或其他大小修飾符。

同樣地,在 verticalScroll 等捲動容器中放置使用 WindowInsetsRulers 的可組合函式,可能會導致非預期行為,因為捲動容器會提供無限制的高度限制,這與尺規的邏輯不相容。