先決條件
- 專案必須使用 Android Jetpack Compose。
- 專案必須將目標 SDK 設為 35 或更高。若 SDK 低於 35,請將 SDK 提升至 35。
步驟 1:規劃
- 找出並分析所有 Activity 類別,檢測哪些已有邊到邊支援。對於每個尚未啟用邊到邊的 Activity,規劃使其啟用邊到邊。
- 在每個 Activity 中,找出並分析所有列表與 FAB 元件,檢測哪些已有邊到邊支援。對於每個尚未具備邊到邊支援的元件,規劃使其具備邊到邊支援。
- 在每個 Activity 中,掃描
TextField、OutlinedTextField或BasicTextField。若找到,則您必須依照此技能的 IME 章節,確認 IME 不會隱藏輸入欄位。
步驟 2:新增邊到邊支援
- 在每個尚未呼叫
enableEdgeToEdge的 Activity 的onCreate方法中,於setContent之前加入enableEdgeToEdge。 - 在 AndroidManifest.xml 中,為所有使用軟體鍵盤的 Activity 新增
android:windowSoftInputMode="adjustResize"。
步驟 3:應用插入區(Insets)
-
應用程式必須套用系統插入區,或將內容對齊參考線,以確保關鍵 UI 仍可點擊。請僅選擇一種方法,以避免雙重內距:
- (首選) 當可行時,使用
Scaffold並將PaddingValues傳遞給內容 lambda。
Scaffold { innerPadding -> // innerPadding 會考慮系統列及任何 Scaffold 元件 LazyColumn( modifier = Modifier .fillMaxSize() .consumeWindowInsets(innerPadding), contentPadding = innerPadding ) { /* 內容 */ } }-
(首選) 當可行時,使用 Material 元件中的自動插入區處理或內距修飾詞。
- Material 3 元件會為其自身元件管理安全區域,包括:
TopAppBarSmallTopAppBarCenterAlignedTopAppBarMediumTopAppBarLargeTopAppBarBottomAppBarModalDrawerSheetDismissibleDrawerSheetPermanentDrawerSheetModalBottomSheetNavigationBarNavigationRail
- 對於 Material 2 元件,使用
windowInsets參數手動為BottomAppBar、TopAppBar與BottomNavigation套用插入區。請勿對父容器套用內距;取而代之的是直接將插入區傳遞給應用程式列元件。對父容器套用內距會阻止應用程式列的背景繪製到系統列的區域。例如,對於TopAppBar,僅選擇以下選項之一:- (首選)
TopAppBar(windowInsets = AppBarDefaults.topAppBarWindowInsets) TopAppBar(windowInsets = WindowInsets.systemBars.exclude(WindowInsets.navigationBars))TopAppBar(windowInsets = WindowInsets.systemBars.add(WindowInsets.captionBar))
- (首選)
- Material 3 元件會為其自身元件管理安全區域,包括:
-
對於 Scaffold 之外的元件,使用內距修飾詞,例如
Modifier.safeDrawingPadding()或Modifier.windowInsetsPadding(WindowInsets.safeDrawing)。Box( modifier = Modifier .fillMaxSize() .safeDrawingPadding() ) { Button( onClick = {}, modifier = Modifier.align(Alignment.BottomCenter) ) { Text("Login") } } -
對於深度巢狀且有過多內距的元件,使用
WindowInsetsRulers(例如Modifier.fitInside(WindowInsetsRulers.SafeDrawing.current))。請參閱 IME 章節的代碼範例。 -
當您需要某個元素(例如自訂標頭或裝飾性遮罩)與系統列具有相同尺寸時,使用插入區尺寸修飾詞(例如
Modifier.windowInsetsTopHeight(WindowInsets.systemBars))。 請參閱 列表 章節的代碼範例。
- (首選) 當可行時,使用
自適應 Scaffold
NavigationSuiteScaffold會為其自身元件(如NavigationRail或NavigationBar)管理安全區域。然而,自適應 Scaffold(例如NavigationSuiteScaffold、ListDetailPaneScaffold)不會將PaddingValues傳播給其內部內容。您必須將插入區應用於個別畫面或元件(例如列表contentPadding或 FAB 內距),如 步驟 3 中所述。請勿將safeDrawingPadding或類似修飾詞應用於NavigationSuiteScaffold父容器。這會剪裁並阻止邊到邊畫面。
IME
- 對於每個使用軟體鍵盤的 Activity,請檢查 AndroidManifest.xml 中是否設定了
android:windowSoftInputMode="adjustResize"。請勿使用SOFT_INPUT_ADJUST_RESIZE,因為它已被棄用。然後,維持輸入欄位的焦點。選擇其中一種方式:-
- (首選) 在內容容器中新增
Modifier.fitInside(WindowInsetsRulers.Ime.current)。這比使用imePadding()更佳,因為它減少了因忘記在上游層級中消費插入區而導致的抖動和額外內距。
- (首選) 在內容容器中新增
-
- 在內容容器中新增
imePadding。此內距修飾詞必須放在Modifier.verticalScroll()之前。如果父容器已透過contentWindowInsets(例如contentWindowInsets = WindowInsets.safeDrawing)處理 IME,請勿使用Modifier.imePadding()。這樣做會導致雙重內距。
- 在內容容器中新增
-
使用 Scaffold 的 IME 代碼模式
正確
正確,因為 contentWindowInsets 包含 IME 插入區,並作為 innerPadding 傳遞給內容 lambda。
// 正確
Scaffold(contentWindowInsets = WindowInsets.safeDrawing) { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.consumeWindowInsets(innerPadding)
.verticalScroll(rememberScrollState())
) { /* 內容 */ }
}
正確,因為 fitInside 會使內容適應 IME 插入區,無論 contentWindowInsets 為何。
// 正確
Scaffold() { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.consumeWindowInsets(innerPadding)
.fitInside(WindowInsetsRulers.Ime.current)
.verticalScroll(rememberScrollState())
) { /* 內容 */ }
}
正確,因為預設的 contentWindowInsets 不包含 IME 插入區,而 imePadding() 會套用 IME 插入區:
// 正確
Scaffold() { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.consumeWindowInsets(innerPadding)
.imePadding()
.verticalScroll(rememberScrollState())
) { /* 內容 */ }
}
錯誤
錯誤,因為當 IME 開啟時會出現過多的內距。IME 插入區被套用了兩次,一次是透過傳遞的 contentWindowInsets 值中的 innerPadding(該值包含 IME 插入區),另一次是透過 imePadding:
// 錯誤
Scaffold(contentWindowInsets = WindowInsets.safeDrawing) { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.imePadding()
.verticalScroll(rememberScrollState())
) { /* 內容 */ }
}
錯誤,因為 IME 會覆蓋內容。Scaffold 的預設 contentWindowInsets 不包含 IME 插入區。
// 錯誤
Scaffold() { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.verticalScroll(rememberScrollState())
) { /* 內容 */ }
}
不使用 Scaffold 的 IME 代碼模式
正確
以下代碼範例不會導致過多的內距。
// 正確
Box(
// 插入區已被消費
modifier = Modifier.safeDrawingPadding() // 或 imePadding(), safeContentPadding(), safeGesturesPadding()
) {
Column(
modifier = Modifier.imePadding()
) { /* 內容 */ }
}
// 正確
Box(
// 插入區已被消費
modifier = Modifier.windowInsetsPadding(WindowInsets.safeDrawing) // 或 WindowInsets.ime, WindowInsets.safeContent, WindowInsets.safeGestures
) {
Column(
modifier = Modifier.imePadding()
) { /* 內容 */ }
}
// 正確
Box(
// 插入區未被消費,但因 fitInside 而無關緊要
modifier = Modifier.padding(WindowInsets.safeDrawing.asPaddingValues()) // 或 WindowInsets.ime.asPaddingValues(), WindowInsets.safeContent.asPaddingValues(), WindowInsets.safeGestures.asPaddingValues()
) {
Column(
modifier = Modifier
.fillMaxSize()
.fitInside(WindowInsetsRulers.Ime.current)
) { /* 內容 */ }
}
錯誤
以下代碼範例會導致過多的內距,因為 IME 插入區被套用了兩次:
// 錯誤
Box(
// 插入區未被消費
modifier = Modifier.padding(WindowInsets.safeDrawing.asPaddingValues()) // 或 WindowInsets.ime.asPaddingValues(), WindowInsets.safeContent.asPaddingValues(), WindowInsets.safeGestures.asPaddingValues()
) {
Column(
modifier = Modifier.imePadding()
) { /* 內容 */ }
}
導覽列對比度與系統列圖示
-
如果 Activity 使用來自
WindowCompat的enableEdgeToEdge,您必須將isAppearanceLightNavigationBars和isAppearanceLightStatusBars設為裝置主題的相反值(適用於支援淺色和深色主題的應用程式),以使系統列圖示清晰可讀。建議在您的主題檔案中執行此操作。如果 Activity 使用來自ComponentActivity的enableEdgeToEdge,請不要這樣做,因為它會自動處理圖示顏色。// 僅在從 `WindowCompat` 呼叫 `enableEdgeToEdge` 時使用。 // 套用到您的主題檔案。 @Composable fun MyTheme( darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit ) { val view = LocalView.current if (!view.isInEditMode) { SideEffect { val window = (view.context as? Activity)?.window ?: return@SideEffect val controller = WindowCompat.getInsetsController(window, view) // 淺色模式 (!darkTheme) 使用深色圖示,深色模式使用淺色圖示 controller.isAppearanceLightStatusBars = !darkTheme controller.isAppearanceLightNavigationBars = !darkTheme } } MaterialTheme(content = content) } -
如果任何畫面使用帶有底部列(例如
BottomAppBar、NavigationBar)的Scaffold或NavigationSuiteScaffold,請在對應的 Activity 中為 SDK 29+ 設定window.isNavigationBarContrastEnforced = false。這可以防止系統在導覽列上添加半透明背景,確保您的底部列顏色延伸到螢幕底部。
列表
- 將插入區內距(如
Scaffold的innerPadding)套用到可捲動元件(例如LazyColumn、LazyRow)的contentPadding參數。請勿將其作為Modifier.padding()套用到列表的父容器,因為這會剪裁內容並阻止其在系統列後方捲動。 - 建立一個覆蓋系統列的半透明 Composable,以便圖示仍然清晰可讀。
class SystemBarProtectionSnippets : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// enableEdgeToEdge 設定 window.isNavigationBarContrastEnforced = true
// 用於為三按鈕導覽添加半透明遮罩
enableEdgeToEdge()
setContent {
MyTheme {
// 主要內容
MyContent()
// 在繪製主要內容之後,繪製狀態列保護
StatusBarProtection()
}
}
}
}
@Composable
private fun StatusBarProtection(
color: Color = MaterialTheme.colorScheme.surfaceContainer,
) {
Spacer(
modifier = Modifier
.fillMaxWidth()
.height(
with(LocalDensity.current) {
(WindowInsets.statusBars.getTop(this) * 1.2f).toDp()
}
)
.background(
brush = Brush.verticalGradient(
colors = listOf(
color.copy(alpha = 1f),
color.copy(alpha = 0.8f),
Color.Transparent
)
)
)
)
}
對話框
如果以下兩個條件皆成立,則對話框為全螢幕,且必須設為邊到邊:
DialogProperties包含usePlatformDefaultWidth = false。- 對話框呼叫
Modifier.fillMaxSize()。
若要將全螢幕對話框設為邊到邊,請在 DialogProperties 中設定 decorFitsSystemWindows = false。
Dialog(
onDismissRequest = { /* 處理關閉 */ },
properties = DialogProperties(
// 1. 允許對話框橫跨螢幕的完整寬度
usePlatformDefaultWidth = false,
// 2. 允許對話框在狀態列和導覽列後方繪製
decorFitsSystemWindows = false
)
) { /* 內容 */ }
檢查清單
- [ ] 每個
Activity是否都呼叫了enableEdgeToEdge()? - [ ]
AndroidManifest.xml中是否設定了adjustResize? - [ ] 每個
TextField、OutlinedTextField或BasicTextField是否都有一個設定為WindowInsets.safeDrawing或WindowInsets.ime的包含imePadding()、fitInside、Modifier.safeDrawingPadding()、Modifier.safeContentPadding()、Modifier.safeGesturesPadding()或contentWindowInsets的父容器? - [ ] 第一個和最後一個列表項目是否透過將插入區傳遞給
contentPadding而遠離系統列繪製? - [ ] FAB 是否透過位於 Scaffold 內部或套用
Modifier.safeDrawingPadding()而在導覽列上方繪製? - [ ] 專案是否可建置?請執行
./gradlew build以確認。