在應用程式中以無邊框方式顯示內容,並在 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 重疊,因此應用程式 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

代表瀑布螢幕曲線區域的插邊。瀑布式螢幕的螢幕邊緣有弧形區域,螢幕會從這裡開始沿著裝置兩側延伸。

這些類型可歸納為三種「安全」內嵌類型,可確保內容不會遭到遮蔽:

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

插邊設定

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

  1. 指定 SDK 35 以上版本,在 Android 15 以上版本中強制採用無邊框設計。您的應用程式 會在系統 UI 後方顯示。您可以透過處理內嵌調整應用程式的使用者介面。
  2. 您也可以選擇在 Activity.onCreate() 中呼叫 enableEdgeToEdge(),讓應用程式在舊版 Android 上呈現無邊框畫面。
  3. 在活動的 AndroidManifest.xml 項目中設定 android:windowSoftInputMode="adjustResize"。這項設定可讓應用程式以內嵌方式接收軟體 IME 的大小,以便在 IME 在應用程式中顯示和消失時,適當地填充及安排內容。

    <!-- 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

活動一旦開始控管所有插邊,您就可以使用 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
            )
        )
    }
}

輸入法編輯器關閉時,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 或固定高度 空格字元:

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 Compose 的階段有特殊關係。

內嵌值會在組合階段之後更新,但在版面配置階段之前更新。這表示讀取組合中插邊的值 通常會使用影格延遲的插邊值。本頁所述的內建輔助鍵,可延遲使用內嵌值,直到版面配置階段為止,藉此確保內嵌值會在更新時用於相同的框架。

使用 WindowInsets 的鍵盤 IME 動畫

您可以將 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.consumeWindowInsets敬上 至 false

資源