關於 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。

使用 Modifier.fitInside 避免 IME

如要使用 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 或其他大小修飾符。

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