Android 平台負責繪製系統 UI,例如狀態列和導覽列。無論使用者使用哪個應用程式,系統都會顯示這個 UI。
WindowInsets
會提供系統 UI 相關資訊,確保應用程式在正確的區域繪製,且 UI 不會遭系統 UI 遮蔽。
在 Android 14 (API 級別 34) 以下版本中,應用程式的 UI 預設不會在系統列下方繪製,也不會顯示缺口。
在 Android 15 (API 級別 35) 以上版本中,如果應用程式指定 SDK 35 以上版本,應用程式會在系統列下方繪製並顯示缺口。這可提供更流暢的使用者體驗,並讓應用程式充分運用可用的視窗空間。
在系統 UI 後方顯示內容稱為「無邊框」。您將在這頁面中瞭解不同類型的內嵌,以及如何進行邊緣對齊,並使用內嵌 API 為 UI 製作動畫,確保應用程式內容不會遭系統 UI 元素遮蔽。
內嵌基本概念
當應用程式採用從邊到邊的螢幕時,您必須確保系統 UI 不會遮蓋重要內容和互動。舉例來說,如果按鈕位於導覽列後方,使用者可能無法點選該按鈕。
系統 UI 的大小和放置位置資訊會透過 insets 指定。
系統 UI 的每個部分都有對應的內嵌類型,可說明其大小和放置位置。舉例來說,狀態列插邊提供狀態列的大小和位置,而導覽列插邊則提供導覽列的大小和位置。每種內嵌類型都包含四個像素尺寸:頂端、左側、右側和底部。這些尺寸會指定系統 UI 從應用程式視窗對應側延伸的距離。因此,為了避免與該類型的系統 UI 重疊,應用程式 UI 必須以該數值內縮。
您可以透過 WindowInsets
使用下列內建的 Android 內嵌類型:
說明狀態列的內嵌區域。這是頂端系統 UI 列,包含通知圖示和其他指標。 |
|
可顯示狀態列的內嵌區域。如果狀態列目前處於隱藏狀態 (因為進入全螢幕模式),則主要狀態列內嵌會是空白,但這些內嵌會是空白。 |
|
說明導覽列的內嵌區域。這些是裝置左側、右側或底部的系統 UI 列,用於說明工作列或導覽圖示。這些值會根據使用者偏好的導覽方法和與工作列互動方式,在執行階段變更。 |
|
導覽列內嵌,用於顯示導覽列時。如果導覽列目前處於隱藏狀態 (因為進入全螢幕模式),則主導覽列內嵌項目會是空白,但這些內嵌項目不會是空白。 |
|
插圖說明任意形式視窗中的系統 UI 視窗裝飾,例如頂部標題列。 |
|
顯示字幕時的字幕列內嵌。如果目前隱藏了字幕列,主字幕列內嵌會是空白,但這些內嵌會是空白。 |
|
系統列插邊的聯合,包括狀態列、導覽列和說明文字列。 |
|
系統資訊列的內嵌邊距,用於顯示資訊列時。如果系統資訊列目前處於隱藏狀態 (因為進入沉浸式全螢幕模式),則主要系統資訊列內嵌會是空白,但這些內嵌會是空白。 |
|
插圖說明軟體鍵盤占用的底部空間量。 |
|
插圖說明軟體鍵盤在前面目前鍵盤動畫的空間量。 |
|
插圖說明螢幕鍵盤動畫後,軟體鍵盤會佔用多少空間。 |
|
一種邊距,可說明導覽 UI 的詳細資訊,提供「輕觸」的空間量,由系統而非應用程式處理。對於含有手勢導覽功能的透明導覽列,部分應用程式元素可透過系統導覽 UI 輕觸。 |
|
可點選元素的內嵌邊距,用於顯示時使用。如果可點選元素目前處於隱藏狀態 (因為進入全螢幕模式),則主要可點選元素內嵌會為空白,但這些內嵌會非空白。 |
|
邊框代表系統會攔截手勢用於導覽的邊框數量。應用程式可以透過 |
|
系統手勢的子集,系統一律會處理這些手勢,且無法透過 |
|
內嵌表示避免與螢幕凹口 (缺口或針孔) 重疊所需的間距量。 |
|
內嵌圖片代表瀑布式螢幕的弧形區域。瀑布式螢幕的螢幕邊緣有弧形區域,螢幕會從這裡開始沿著裝置兩側延伸。 |
這些類型可歸納為三種「安全」內嵌類型,可確保內容不會遭到遮蔽:
這些「安全」內嵌類型會根據基礎平台內嵌,以不同方式保護內容:
- 請使用
WindowInsets.safeDrawing
保護不應在任何系統 UI 下方繪製的內容。這是內嵌邊框最常見的用途:避免繪製的內容遭系統 UI 遮蔽 (部分或完全遮蔽)。 - 使用
WindowInsets.safeGestures
保護內容,並設定手勢操作方式。這樣可避免系統手勢與應用程式手勢 (例如底部頁面、輪轉介面或遊戲中的手勢) 發生衝突。 - 使用
WindowInsets.safeContent
做為WindowInsets.safeDrawing
和WindowInsets.safeGestures
的組合,確保內容不會有視覺重疊和手勢重疊的情形。
設定內嵌
如要讓應用程式完全控制繪製內容的位置,請按照下列設定步驟操作。如果未執行這些步驟,應用程式可能會在系統 UI 後方繪製黑色或純色,或是無法與軟體鍵盤同步顯示動畫。
- 指定 Android 15 以上版本的 SDK 35 以上版本,即可在 Android 15 以上版本強制執行邊到邊。應用程式會顯示在系統 UI 後方。您可以透過處理內嵌處理應用程式的 UI。
- 您也可以選擇在
Activity.onCreate()
中呼叫enableEdgeToEdge()
,讓應用程式在舊版 Android 上呈現無邊框畫面。 在活動的
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)
。其他內嵌類型也有類似的修飾符。
內嵌大小修飾符
下列修飾符會將元件大小設為內嵌大小,藉此套用視窗內嵌的數量:
將 windowInsets 的起始側邊設為寬度 (例如 |
|
將 windowInsets 的端側套用為寬度 (例如 |
|
將 windowInsets 的頂端邊緣套用為高度 (例如 |
|
|
將 windowInsets 的底部套用為高度 (例如 |
這些輔助鍵特別適合用於調整 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 ) ) } }
IME 關閉時,imePadding()
修飾符不會套用邊框,因為 IME 沒有高度。由於 imePadding()
修飾符不會套用邊框間距,因此不會使用任何內嵌,且 Spacer
的高度會是系統資訊列底部邊緣的大小。
IME 開啟時,IME 內嵌動畫會與 IME 大小相符,而 imePadding()
修飾符會開始套用底部邊距,在 IME 開啟時調整 LazyColumn
大小。當 imePadding()
修飾符開始套用底部邊框時,也會開始使用該內嵌量。因此,Spacer
的高度會開始降低,因為系統資訊列的部分間距已由 imePadding()
修飾符套用。當 imePadding()
修飾符套用比系統資訊列更大的底部邊框間距時,Spacer
的高度會為零。
當 IME 關閉時,變化會以相反的方向進行:當 imePadding()
套用的高度小於系統資訊列的底部時,Spacer
會從零高度開始展開,直到 IME 完全動畫結束時,Spacer
才會與系統資訊列底部高度相符。
這項行為是透過所有 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 的階段有特殊關係。
內嵌值會在組合階段之後更新,但在版面配置階段之前更新。也就是說,讀取合成作業中的內嵌值時,通常會使用延遲一格內嵌的值。本頁所述的內建輔助鍵是為了延遲使用內嵌值,直到版面配置階段為止,藉此確保內嵌值會在更新時用於相同的框架。
使用 WindowInsets
的鍵盤 IME 動畫
您可以將 Modifier.imeNestedScroll()
套用至捲動容器,在捲動至容器底部時自動開啟及關閉 IME。
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") } } }
支援 Material 3 元件的內嵌設定
為方便使用,許多內建的 Material 3 可組合項 (androidx.compose.material3
) 會根據 Material 規格在應用程式中放置可組合項的方式,自行處理插邊。
插邊處理可組合項
以下是自動處理插邊的 Material 元件清單。
應用程式列
TopAppBar
/SmallTopAppBar
/CenterAlignedTopAppBar
/MediumTopAppBar
/LargeTopAppBar
:將系統列的頂端和水平兩側設為邊框,因為系統列會用於視窗頂端。BottomAppBar
:將系統資訊列的底部和水平兩側做為邊框間距。
內容容器
ModalDrawerSheet
/DismissibleDrawerSheet
/PermanentDrawerSheet
(模式導覽匣內的內容):將垂直和開始內嵌套用於內容。ModalBottomSheet
:套用底部內嵌。NavigationBar
:套用底部和水平內嵌。NavigationRail
:套用垂直和開始內嵌。
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
。
系統資訊列保護功能
應用程式指定目標為 SDK 35 以上版本後,系統會強制執行無邊框設計。系統狀態列和手勢導覽列為透明,但三按鈕導覽列則為半透明。
如要移除預設的半透明三按鈕操作模式背景保護措施,請將 Window.setNavigationBarContrastEnforced
設為 false
。
資源
- Android 系統列,系統列設計指南
- Android 版「即時資訊」:功能完整的 Android 應用程式,完全使用 Kotlin 和 Jetpack Compose 建構。
- 因應 Android 15 強制採用的無邊框措施 - 程式碼研究室,逐步說明 Android 15 強制採用的無邊框措施
- 針對 Android 15 無邊框措施的邊框處理提示
- 預覽及測試應用程式的無邊框 UI
- 3 個改善 Android 應用程式體驗的方法:無邊框設計、預測返回和資訊一覽 - YouTube 影片,介紹 Android 15 的無邊框設計強制執行機制
- Edge-to-edge and insets | Compose Tips:YouTube 影片,說明如何處理內嵌內容,以便繪製邊到邊的畫面
為您推薦
- 注意:系統會在 JavaScript 關閉時顯示連結文字
- Material Design 元件和版面配置
- 將
CoordinatorLayout
遷移至 Compose - 其他考量