確保 UI 可搭配視窗插邊運作

活動一旦開始控管所有插邊,您就可以使用 Compose API 確保內容不會遭到遮蔽,且可互動元素不會與系統 UI 重疊。這些 API 也會將應用程式的版面配置與內嵌變更同步。

舉例來說,這是將內嵌邊距套用至整個應用程式內容的最基本方法:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    enableEdgeToEdge()

    setContent {
        Box(Modifier.safeDrawingPadding()) {
            // the rest of the app
        }
    }
}

這個程式碼片段會將 safeDrawing 視窗內嵌區塊套用為應用程式整個內容的邊框間距。雖然這可確保可互動的元素不會與系統 UI 重疊,但也表示沒有任何應用程式會在系統 UI 後方繪製,以達到從邊到邊的效果。如要充分利用整個視窗,您需要針對每個畫面或元件微調套用內嵌的範圍。

所有這些內嵌類型都會自動顯示動畫,並將 IME 動畫回溯至 API 21。擴充功能中,所有使用這些內嵌項目的版面配置也會在內嵌值變更時自動顯示動畫。

使用這些內嵌類型調整可組合項版面配置的方式主要有兩種:邊框修飾符和內嵌大小修飾符。

邊框間距修飾符

Modifier.windowInsetsPadding(windowInsets: WindowInsets) 會將指定的視窗插邊套用為邊框間距,就像 Modifier.padding 一樣。例如,Modifier.windowInsetsPadding(WindowInsets.safeDrawing) 會將安全繪圖插邊套用為 4 邊的邊距。

另外,也提供幾種內建的實用方法,可用於最常見的內嵌類型。Modifier.safeDrawingPadding() 就是這類方法之一,相當於 Modifier.windowInsetsPadding(WindowInsets.safeDrawing)。其他內嵌類型也有類似的修飾符。

內嵌尺寸修飾符

下列修飾符會將元件大小設為內嵌大小,藉此套用視窗內嵌的數量:

Modifier.windowInsetsStartWidth(windowInsets: WindowInsets)

將 windowInsets 的起始側邊設為寬度 (例如 Modifier.width)

Modifier.windowInsetsEndWidth(windowInsets: WindowInsets)

將 windowInsets 的端側套用為寬度 (例如 Modifier.width)

Modifier.windowInsetsTopHeight(windowInsets: WindowInsets)

將 windowInsets 的頂端做為高度 (例如 Modifier.height)

Modifier.windowInsetsBottomHeight(windowInsets: WindowInsets)

將 windowInsets 的底部套用為高度 (例如 Modifier.height)

這些輔助鍵特別適合用於調整 Spacer 的大小,以便佔用內嵌區域的空間:

LazyColumn(
    Modifier.imePadding()
) {
    // Other content
    item {
        Spacer(
            Modifier.windowInsetsBottomHeight(
                WindowInsets.systemBars
            )
        )
    }
}

嵌入式消費

內嵌邊距輔助鍵 (windowInsetsPaddingsafeDrawingPadding 等輔助程式) 會自動使用用於邊距的內嵌邊距部分。在深入組合樹狀結構時,巢狀插邊邊框間距修飾符和插邊大小修飾符會知道外部插邊邊框間距修飾符已使用插邊的部分,並避免重複使用插邊的部分,以免產生過多額外空間。

如果插入邊已被使用,插入邊大小修飾符也會避免重複使用相同的插入邊。不過,由於它們會直接變更自身大小,因此不會自行使用內嵌區塊。

因此,巢狀邊框間距修飾符會自動變更套用至每個可組合函式的邊框間距數量。

以先前的 LazyColumn 範例為例,LazyColumn 會透過 imePadding 輔助鍵調整大小。在 LazyColumn 中,最後一個項目的大小會設為系統列底部的高度:

LazyColumn(
    Modifier.imePadding()
) {
    // Other content
    item {
        Spacer(
            Modifier.windowInsetsBottomHeight(
                WindowInsets.systemBars
            )
        )
    }
}

關閉 IME 時,由於 IME 沒有高度,因此 imePadding() 修飾符不會套用邊框。由於 imePadding() 修飾符不會套用邊框間距,因此不會使用任何內嵌,且 Spacer 的高度會是系統資訊列底部邊緣的大小。

IME 開啟時,IME 內嵌動畫會與 IME 大小相符,而 imePadding() 修飾符會開始套用底部邊距,在 IME 開啟時調整 LazyColumn 大小。當 imePadding() 修飾符開始套用底部邊框時,也會開始使用該內嵌量。因此,Spacer 的高度會開始降低,因為系統資訊列的部分間距已由 imePadding() 修飾符套用。當 imePadding() 修飾符套用比系統資訊列更大的底部邊框間距時,Spacer 的高度會為零。

當 IME 關閉時,變化會以相反的方向進行:當 imePadding() 套用的高度小於系統資訊列的底部時,Spacer 會從零高度開始展開,直到 IME 完全動畫結束時,Spacer 才會與系統資訊列底部高度相符。

圖 2. 使用 TextField 的邊緣到邊緣延遲欄。

這項行為是透過所有 windowInsetsPadding 修飾符之間的通訊完成,且可能受到其他幾種方式的影響。

Modifier.consumeWindowInsets(insets: WindowInsets) 也會以與 Modifier.windowInsetsPadding 相同的方式使用插邊,但不會將已使用的插邊套用為邊框間距。這項功能與內嵌大小修飾符搭配使用時相當實用,可向同胞元件指出已使用特定數量的內嵌:

Column(Modifier.verticalScroll(rememberScrollState())) {
    Spacer(Modifier.windowInsetsTopHeight(WindowInsets.systemBars))

    Column(
        Modifier.consumeWindowInsets(
            WindowInsets.systemBars.only(WindowInsetsSides.Vertical)
        )
    ) {
        // content
        Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime))
    }

    Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.systemBars))
}

Modifier.consumeWindowInsets(paddingValues: PaddingValues) 的行為與使用 WindowInsets 引數的版本非常相似,但會使用任意的 PaddingValues 進行取用。這項資訊可用於通知子項,當邊框內填充修飾符以外的其他機制提供邊框內填充或間距時:Modifier.padding

Column(Modifier.padding(16.dp).consumeWindowInsets(PaddingValues(16.dp))) {
    // content
    Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime))
}

如果需要未經消耗的原始視窗插邊,請直接使用 WindowInsets 值,或使用 WindowInsets.asPaddingValues() 傳回未受消耗影響的插邊 PaddingValues。不過,由於下列警告,請盡可能使用視窗插邊邊框間距修飾符和視窗插邊大小修飾符。

Insets 和 Jetpack Compose 階段

Compose 會使用基礎 AndroidX 核心 API 更新並為內嵌動畫設定動畫效果,而內嵌動畫會使用基礎平台 API 管理內嵌。由於平台行為,內嵌內容與 Jetpack Compose 的階段有特殊關係。

內嵌值會在組合階段之後更新,但在版面配置階段之前更新。也就是說,讀取合成作業中的內嵌值時,通常會使用延遲一格內嵌的值。本頁所述的內建輔助鍵是為了延遲使用內嵌值,直到版面配置階段為止,藉此確保內嵌值會在更新時用於相同的框架。