Compose 中的視窗插邊

Android 平台負責繪製系統 UI,例如 狀態列和導覽列無論 使用者正在使用的應用程式。

WindowInsets 提供系統相關資訊 UI 可確保應用程式繪製在正確的區域,且 UI 不會遭到遮蓋 由系統 UI 產生

採用無邊框設計,在系統資訊列後方繪製
圖 1. 採用無邊框設計,在系統資訊列後方繪製。

在 Android 14 (API 級別 34) 以下版本中,應用程式的 UI 不會在以下之下繪製 系統資訊列和螢幕凹口。

在 Android 15 (API 級別 35) 以上版本中,應用程式會在系統下方繪製 應用程式指定 SDK 35 後,就會呈現凹口。如此一來 順暢的使用者體驗,並充分發揮應用程式的功用 可用的視窗空間

在系統 UI 後方顯示內容稱為「無邊框設計」。在這方面 您將進一步瞭解不同類型的插邊、採用無邊框設計的方法 以及如何使用插邊 API 為使用者介面建立動畫效果,並確保應用程式的內容 不會遭系統 UI 元素遮蓋。

插邊基礎知識

當應用程式採用無邊框設計時,您必須確保 互動不會遭到系統 UI 遮蓋。舉例來說 位於導覽列後方,使用者可能無法點選這個按鈕。

系統 UI 大小及其位置相關資訊 透過 插邊

系統 UI 各部分都有對應的插邊類型, 大小和位置例如,狀態列插邊會提供 和狀態列的位置,導覽列插邊則提供 導覽列的大小和位置每種插邊都包含 像素尺寸:頂端、左側、右側和底部。您可以透過這些維度 系統 UI 會從應用程式視窗的對應兩側延伸。為了避免 與該類型的系統 UI 重疊,因此應用程式 UI 必須透過該類型的系統插邊 金額。

您可以透過 WindowInsets 取得以下內建 Android 插邊類型:

WindowInsets.statusBars

說明狀態列的插邊。這些是頂端系統 UI 列,內含通知圖示和其他指標。

WindowInsets.statusBarsIgnoringVisibility

顯示時間的狀態列插邊。如果狀態列目前處於隱藏狀態 (因為要進入沉浸模式),主要狀態列插邊將呈現空白,但這些插邊不會空白。

WindowInsets.navigationBars

說明導覽列的插邊。這些是裝置左側、右側或底部的系統 UI 列,描述工作列或導覽圖示。這些目錄在執行階段會根據使用者偏好的導覽方法和工作列互動而變更。

WindowInsets.navigationBarsIgnoringVisibility

導覽列插邊用於指定顯示時間。如果導覽列目前處於隱藏狀態 (因為進入全螢幕模式),主導覽列插邊將呈現空白,但這些插邊不會空白。

WindowInsets.captionBar

說明任意形式視窗中 (例如頂端標題列) 的系統 UI 視窗裝飾插邊。

WindowInsets.captionBarIgnoringVisibility

字幕列顯示時間的插邊。如果隱藏了說明文字列,主字幕列插邊將不會顯示,但這些插邊不會空白。

WindowInsets.systemBars

系統列插邊的聯集,包括狀態列、導覽列和說明文字列。

WindowInsets.systemBarsIgnoringVisibility

顯示時的系統資訊列插邊。如果系統資訊列目前處於隱藏狀態 (因為進入了沉浸式全螢幕模式),主系統列插邊將呈現空白,但這些插邊不會空白。

WindowInsets.ime

這些插邊用於說明軟體鍵盤所佔用的空間大小。

WindowInsets.imeAnimationSource

此插邊用於說明軟體鍵盤在目前的鍵盤動畫「之前」佔用的空間大小。

WindowInsets.imeAnimationTarget

此插邊用於說明軟體鍵盤在目前的鍵盤動畫「之後」佔用的空間。

WindowInsets.tappableElement

一種插邊,描述導覽 UI 的詳細資訊,提供「輕觸」的空間大小將由系統處理,而非應用程式。針對採用手勢操作的透明導覽列,部分應用程式元素可以透過系統導覽 UI 輕觸。

WindowInsets.tappableElementIgnoringVisibility

可輕觸元素插邊顯示的時間。如果可輕觸的元素目前處於隱藏狀態 (因為進入全螢幕模式),主要可輕觸的元素插邊將呈現空白,但這些插邊不會空白。

WindowInsets.systemGestures

代表系統要攔截瀏覽手勢的插邊數量。應用程式可以透過 Modifier.systemGestureExclusion 手動指定處理有限數量的手勢。

WindowInsets.mandatorySystemGestures

系統手勢的子集一律會由系統處理,且無法透過 Modifier.systemGestureExclusion 停用。

WindowInsets.displayCutout

這些插邊代表避免與螢幕凹口 (凹口或針孔) 重疊所需的間距量。

WindowInsets.waterfall

代表瀑布螢幕曲線區域的插邊。瀑布顯示畫麵包含沿著螢幕邊緣的弧形區域,其中螢幕開始沿著裝置兩側環繞。

這些類型統整為三種「安全」可確保內容不會 遮蓋:

這些「安全」插邊類型會根據 基礎平台插邊:

插邊設定

如要讓應用程式完全控制繪製內容的位置,請遵循下列設定 100 萬步的訓練如果沒有執行上述步驟,您的應用程式可能會在畫面背後繪製黑色或純色 系統 UI,或是不要與螢幕鍵盤同步動畫。

  1. 指定 SDK 35 以上版本,在 Android 15 以上版本中強制採用無邊框設計。您的應用程式 會在系統 UI 後方顯示。如要調整應用程式的 UI,您可以處理 插邊。
  2. 您可以視需要呼叫 enableEdgeToEdge()Activity.onCreate(),可讓應用程式自動採用無邊框設計 Android 版本。
  3. 在活動的android:windowSoftInputMode="adjustResize" AndroidManifest.xml 個項目。這項設定可讓應用程式接收 做為插邊,可用來設定內容的位置和版面配置 。

    <!-- in your AndroidManifest.xml file: -->
    <activity
      android:name=".ui.MainActivity"
      android:label="@string/app_name"
      android:windowSoftInputMode="adjustResize"
      android:theme="@style/Theme.MyApplication"
      android:exported="true">
    

Compose API

當 Activity 控制所有插邊後,您就可以使用 Compose API 可確保內容不會遭到遮蓋及可互動的元素 與系統 UI 重疊這些 API 也會將您的應用程式版面配置與 插邊變更。

舉例來說,這是將插邊套用至內容的最基本方法 您整個應用程式的作業流程

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

    enableEdgeToEdge()

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

這段程式碼會將 safeDrawing 視窗插邊做為邊框間距, 整個應用程式的內容雖然這樣能確保可互動元素 這也表示應用程式不會在後端 進而實現無邊框效果為了充分利用 視窗,您需要在每個畫面上,微調插邊套用的位置 或是根據元件建構不同元件

這些插邊類型全都會自動使用 IME 動畫製作動畫 已向後移植至 API 21因此,凡是使用這些插邊的版面配置 而且會在插邊值變更時自動產生動畫效果

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

邊框間距修飾符

Modifier.windowInsetsPadding(windowInsets: WindowInsets) 會套用 將特定視窗插邊設為邊框間距,運作原理與 Modifier.padding 相同。 舉例來說,Modifier.windowInsetsPadding(WindowInsets.safeDrawing) 就會套用 安全繪圖插邊做為邊框間距

另也有數種內建公用程式方法適用於最常見的插邊類型。 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
            )
        )
    }
}

插邊消耗量

插邊邊框間距修飾符 (windowInsetsPadding 和輔助程式,例如 safeDrawingPadding),會自動取用 套用為邊框間距深入研究組合樹狀結構時,巢狀插邊 邊框間距修飾符和插邊大小修飾符知道 插邊已被外部插邊邊框間距修飾符使用,且 重複使用插邊的相同部分反而會導致 額外空間

插邊大小修飾符也避免重複使用相同部分插邊 如果已消耗插邊然而,由於他們變更了 就不會使用插邊。

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

查看與先前相同的 LazyColumn 範例,LazyColumn 處於 由 imePadding 修飾符調整大小。LazyColumn 中的最後一個項目是 調整成系統資訊列底部的高度:

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

輸入法編輯器關閉時,imePadding() 修飾符不會套用邊框間距,因為 輸入法編輯器沒有高度由於 imePadding() 修飾符不會套用任何邊框間距, 未耗用任何插邊,而 Spacer 的高度會是 位於系統資訊列底部的底部

輸入法編輯器開啟時,IME 插邊會配合輸入法編輯器的大小進行動畫, imePadding() 修飾符開始套用底部邊框間距,以調整 LazyColumn 以開啟輸入法編輯器。開始套用 imePadding() 修飾符時 底部邊框間距也會開始耗用這麼多的插邊。因此, Spacer 的高度開始減少,做為系統間距的一部分 imePadding() 修飾符已套用長條。產生 imePadding() 修飾符套用的底部邊框間距較大 大於系統資訊列,Spacer 的高度為零。

輸入法編輯器關閉後,變更會反向生效:Spacer 開始 當 imePadding() 套用的值低於 系統資訊列底部,直到最後 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 或固定高度 空格字元:

@OptIn(ExperimentalLayoutApi::class)
Column(Modifier.padding(16.dp).consumeWindowInsets(PaddingValues(16.dp))) {
    // content
    Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime))
}

如果需要使用原始視窗插邊而不使用,請使用 直接WindowInsets值,或使用 WindowInsets.asPaddingValues() 將 會傳回不受用量影響的插邊 PaddingValues。 不過,基於下方注意事項,建議使用視窗插邊邊框間距 修飾符和視窗插邊大小修飾符。

插邊和 Jetpack Compose 階段

Compose 會使用基礎 AndroidX 核心 API 來更新插邊並加上動畫效果。 這項服務會使用基礎平台 API 管理插邊正因這個平台 插邊與 Jetpack 的階段之間有特殊的關係 撰寫

插邊值是在組成階段之後更新,但更新前 版面配置階段這表示讀取組合中插邊的值 通常會使用影格延遲的插邊值。內建 本頁說明的修飾符的設計是為了讓系統使用 直到版面配置階段為止,確保使用插邊值 相同的影格也會同步更新

使用 WindowInsets 的鍵盤輸入法編輯器動畫

您可以將 Modifier.imeNestedScroll() 套用至捲動容器,以開啟及 捲動至容器底部時自動關閉輸入法編輯器。

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

        WindowCompat.setDecorFitsSystemWindows(window, false)

        setContent {
            MaterialTheme {
                MyScreen()
            }
        }
    }
}

@OptIn(ExperimentalLayoutApi::class)
@Composable
fun MyScreen() {
    Box {
        LazyColumn(
            modifier = Modifier
                .fillMaxSize() // fill the entire window
                .imePadding() // padding for the bottom for the IME
                .imeNestedScroll(), // scroll IME at the bottom
            content = { }
        )
        FloatingActionButton(
            modifier = Modifier
                .align(Alignment.BottomEnd)
                .padding(16.dp) // normal 16dp of padding for FABs
                .navigationBarsPadding() // padding for navigation bar
                .imePadding(), // padding for when IME appears
            onClick = { }
        ) {
            Icon(imageVector = Icons.Filled.Add, contentDescription = "Add")
        }
    }
}

動畫顯示的是上下捲動的 UI 元素,以便使用鍵盤
圖 3.IME 動畫

Material 3 元件的插邊支援

為方便使用,系統內建許多 Material 3 可組合函式 (androidx.compose.material3)。 根據可組合函式在應用程式中的放置方式,自行處理插邊 指定的 Pod 資源

插邊處理可組合項

下方列有Material Design 清單 元件 會自動處理插邊。

應用程式列

內容容器

Scaffold

根據預設 Scaffold 提供插邊做為參數 paddingValues,供您使用和使用。 Scaffold 不會將插邊套用至內容;而這份責任歸您所有 舉例來說,如要利用 Scaffold 內的 LazyColumn 使用這些插邊:

Scaffold { innerPadding ->
    // innerPadding contains inset information for you to use and apply
    LazyColumn(
        // consume insets as scaffold doesn't do it by default
        modifier = Modifier.consumeWindowInsets(innerPadding),
        contentPadding = innerPadding
    ) {
        items(count = 100) {
            Box(
                Modifier
                    .fillMaxWidth()
                    .height(50.dp)
                    .background(colors[it % colors.size])
            )
        }
    }
}

覆寫預設插邊

您可以變更傳遞至可組合函式的 windowInsets 參數 設定可組合函式的行為這個參數可以是不同類型的 要改為套用的視窗插邊,或傳送空白執行個體時停用: WindowInsets(0, 0, 0, 0)

舉例來說,如要停用 LargeTopAppBar、 將 windowInsets 參數設為空白執行個體:

LargeTopAppBar(
    windowInsets = WindowInsets(0, 0, 0, 0),
    title = {
        Text("Hi")
    }
)

與 View 系統插邊互通

當螢幕同時擁有 View 和 位於同一階層的 Compose 程式碼。在這種情況下,您必須 哪些應使用插邊,哪些則應忽略插邊。

舉例來說,如果最外層的版面配置是 Android View 版面配置,則您應 請使用 View 系統中的插邊,並針對 Compose 忽略這些插邊。 或者,如果最外的版面配置是可組合項,則應使用 Compose 中的插邊,然後據此為 AndroidView 可組合函式加上邊框間距。

根據預設,每個 ComposeView 都會使用 WindowInsetsCompat 用量。如要變更這項預設行為,請設定 ComposeView.consumeWindowInsetsfalse

資源