讓應用程式 UI 適應不同 Android 裝置(包括手機、平板、摺疊機、筆電、桌上型電腦、電視、Auto 和 XR)的指示。包含如何使用 Compose MediaQuery API 處理不同視窗尺寸、指向裝置(如滑鼠)和文字輸入裝置(如鍵盤)。也涵蓋使用 Navigation3 Scenes 的多窗格版面、具有不同目標尺寸的適應性 UI 元件(如按鈕),以及使用 Compose Grid 和 FlexBox API 的適應性版面(包括導覽區域 - 導覽欄和導覽列)。
先決條件
應用程式必須:
- 所有畫面都使用 Compose。如果仍使用 Fragments 或 Views,建議使用 XML 轉 Compose 技能來遷移這些畫面。
- 使用 Jetpack Navigation 3。如果沒有,建議使用 Navigation 3 技能來遷移應用程式。
讓應用程式具備適應性的工作流程
若要讓應用程式具備適應性,請依照下列步驟(或依任務調整的子集)進行。
- 步驟 1:驗證目前的 UI
- 步驟 2:讓導覽列具備適應性
- 步驟 3:新增多窗格版面
- 步驟 4:透過變更欄數讓垂直清單具備適應性
- 步驟 5:捲動時隱藏應用程式列
步驟 1. 驗證目前的 UI
確保存在螢幕截圖測試,以驗證目前 UI 在不同外型規格下的表現。如果不存在,請新增 Compose Preview 螢幕截圖測試工具。使用下列註解為所有主要外型規格建立預覽。例如:
@Preview(name = "Phone", device = Devices.PHONE, showBackground = true)
@Preview(name = "Foldable", device = Devices.FOLDABLE, showBackground = true)
@Preview(name = "Tablet", device = Devices.TABLET, showBackground = true)
@Preview(name = "Desktop", device = Devices.DESKTOP, showBackground = true)
annotation class FormFactorPreviews
@PreviewTest
@FormFactorPreviews
@Composable
fun FeedScreenPreview() {
SnippetsTheme {
Box {
Text("My Screen")
}
}
}
<br />
步驟 2. 讓導覽列具備適應性
底部導覽列是針對使用者以直向手持手機時的觸控輸入最佳化。在較大螢幕的手持裝置(如平板和展開的摺疊機)上,導覽區域必須可從螢幕邊緣存取(導覽欄)。
如果需要為內容提供更多螢幕空間,請隱藏導覽區域。範例包括:
- 當使用者向下捲動時隱藏導覽列,向上捲動時再次顯示。假設是當使用者向下捲動時,他們正在消費內容,但向上捲動時,他們試圖離開該內容。
- 當導覽區域的內容造成干擾時隱藏。例如,在相機預覽或顯示全螢幕照片時。
當詳細畫面在手機上以全螢幕顯示時,在較大螢幕上必須停用全螢幕模式。
遷移步驟:
- 找出現有的導覽列。
- 將每個項目轉換為
NavigationSuiteItem。 - 判斷導覽列的可見性是否會變更。例如,如果它包在
AnimatedContent或AnimatedVisibilitycomposable 中。如果是,請遵循「控制導覽區域可見性」中的指引。 - 將容納導覽列的容器(通常是
Scaffold)替換為 Material 3 適應性版面配置程式庫中的NavigationSuiteScaffold。 - 使用
NavigationSuiteScaffold的navigationItems參數提供導覽項目。
步驟 2.1. 控制導覽區域可見性
如果導覽列的可見性會變更 - 在某些情境或某些畫面上隱藏 - 則必須使用適應性導覽區域維持此行為。這是透過 NavigationSuiteScaffold 的 state 參數完成。
遷移步驟:
- 找出導覽列隱藏的情境。這通常使用布林變數來表示可見性。使用
isNavBarVisible或shouldShowNavBar作為變數名稱。 - 使用
rememberNavigationSuiteScaffoldState()建立NavigationSuiteScaffoldState實例,並將其傳遞給NavigationSuiteScaffold。 - 當導覽區域可見性變更時,使用
LaunchedEffect在NavigationSuiteScaffoldState上呼叫show或hide。
例如:
// 將此變數傳遞給任何需要控制導覽區域可見性的 composable
var isNavBarVisible by remember { mutableStateOf(true) }
val scaffoldVisibilityState = rememberNavigationSuiteScaffoldState()
NavigationSuiteScaffold(
navigationSuiteItems = navItems,
state = scaffoldVisibilityState
) {
// 主要內容
}
LaunchedEffect(isNavBarVisible){
if (isNavBarVisible) {
scaffoldVisibilityState.show()
} else {
scaffoldVisibilityState.hide()
}
}
<br />
步驟 3. 使用 Navigation 3 Scenes 新增多窗格版面
分析程式碼庫,尋找相關畫面 - 在一個畫面中點擊某個項目會開啟另一個顯示與第一個畫面相關資訊的畫面。有兩種典型的畫面關係:清單-詳細和支援窗格。
重要:您必須使用 Navigation 3 SceneStrategy 方法來實作多窗格版面。請勿使用 ListDetailPaneScaffold 或 SupportingPaneScaffold。
步驟 3.1. 清單-詳細
找出清單和詳細畫面
清單-詳細版面顯示項目清單(這是清單畫面),點擊項目會開啟一個新畫面,顯示該項目的更多詳細資訊(詳細畫面)。
典型用法包括生產力應用程式,如電子郵件、筆記和訊息。
除非明確要求,否則當詳細內容需要大量螢幕空間(例如,受益於全螢幕呈現的圖片或媒體)時,請避免使用此模式。
新增 Material 清單-詳細 SceneStrategy
- 新增
androidx.compose.material3.adaptive:adaptive-navigation3程式庫 - 使用
rememberListDetailSceneStrategy建立androidx.compose.material3.adaptive.navigation3.ListDetailSceneStrategy - 使用
sceneStrategies參數將ListDetailSceneStrategy傳遞給NavDisplay
使用中繼資料識別清單和詳細畫面
- 使用
entry(metadata = ...)或NavEntry(metadata = ...)將中繼資料新增至清單項目,使用ListDetailSceneStrategy.listPane(detailPlaceholder = { <placeholder composable> })。 - 使用
detailPlaceholder參數在未選取任何清單項目時,在詳細畫面上新增佔位內容。 - 使用
ListDetailSceneStrategy.detailPane()將中繼資料新增至詳細項目。
重要考量
- 當詳細畫面在手機上以全螢幕顯示其內容(內容填滿整個螢幕,列或欄隱藏)時,如果它是清單-詳細版面的一部分,則必須停用全螢幕模式。
- 在清單-詳細版面中,詳細畫面不得顯示返回箭頭。
如需參考實作,請參閱 Nav3 Material 清單詳細食譜。
步驟 3.2. 支援窗格
找出支援窗格畫面,其中主畫面顯示單一項目,選取它會開啟一個顯示更多詳細資訊的「支援畫面」。支援畫面補充主畫面,並顯示在支援窗格中。
新增 Material 支援窗格 SceneStrategy
- 如果尚未新增,請新增
androidx.compose.material3.adaptive:adaptive-navigation3程式庫 - 使用
rememberSupportingPaneSceneStrategy建立androidx.compose.material3.adaptive.navigation3.SupportingPaneSceneStrategy - 使用
sceneStrategies參數將SupportingPaneSceneStrategy傳遞給NavDisplay
使用中繼資料識別主畫面與支援畫面
- 使用
entry(metadata = ...)或NavEntry(metadata = ...)將中繼資料新增至主項目,使用SupportingPaneSceneStrategy.mainPane() - 使用
SupportingPaneSceneStrategy.supportingPane()將中繼資料新增至支援項目
步驟 3.3. 執行螢幕截圖測試
如果您已進行變更,請記錄新的參考檔案。要求使用者視覺驗證新版面是否正確。
步驟 4. 透過變更欄數讓垂直清單具備適應性
步驟 4.1. 讓惰性清單具備適應性
尋找下列垂直清單 composable:LazyColumn、LazyVerticalGrid、LazyVerticalStaggeredGrid。
遷移步驟:
- 為欄選擇合適的最小寬度(以 dp 為單位)。項目在此寬度下必須清晰可見。
- 對於
LazyColumn:變更為LazyVerticalGrid,並遵循後續指示 - 對於
LazyVerticalGrid:將columns參數變更為使用GridCells.Adaptive(<width>.dp) - 對於
LazyVerticalStaggeredGrid:將columns參數變更為使用StaggeredGridCells.Adaptive(<width>.dp)
步驟 4.2. 將非惰性清單遷移至 Grid
警告:Grid 是從 Compose 1.11.0-beta01 開始提供的實驗性 API。請與使用者確認他們願意在程式碼中使用實驗性 API。
尋找任何包含多個相同類型項目的 Column,並將其替換為 Grid。請勿將其替換為 LazyVerticalGrid 或任何其他惰性版面。請勿將 Grid 放在現有的 Column 內。完全替換它。
Grid 透過提供 lambda(GridConfigurationScope 的擴充函式)給其 config 參數來設定。在 lambda 內部,constraints 提供網格容器的最小和最大尺寸,可用於根據可用大小變更列數和欄數。例如,下列程式碼設定 Grid,使得當可用寬度為:
- 小於 800dp 時,使用 2x4 網格
- 800dp 或以上時,使用 4x2 網格
Grid(
config = {
val maxWidthDp = constraints.maxWidth.toDp()
val (cols, rows) = if (maxWidthDp < 800.dp){
2 to 4
} else{
4 to 2
}
val gapSizeDp = 8.dp
val cellSize = ((maxWidthDp - (gapSizeDp * (cols - 1))) / cols).coerceAtLeast(0.dp)
repeat(cols) { column(cellSize) }
repeat(rows) { row(cellSize) }
gap(gapSizeDp)
}
) { /** items **/ }
<br />
Grid 是實驗性 API,因此請將 @OptIn(ExperimentalGridApi::class) 註解新增至任何使用它的函式。
步驟 5:捲動時隱藏應用程式列
在具有多個頂層目的地的應用程式中,每個畫面必須獨立管理自己的應用程式列狀態。有兩種主要的捲動行為:
exitUntilCollapsedScrollBehavior:向下捲動時隱藏,向上捲動時保持隱藏,直到到達頂端(偏移 0)。enterAlwaysScrollBehavior:向下捲動時隱藏,向上捲動時立即顯示。
最後步驟:建置和測試
建置應用程式並執行本機測試。如果專案有螢幕截圖測試,請執行它們,但不要更新參考影像。提示使用者檢視螢幕截圖差異後再進行更新。
實驗性適應性 API 的其他文件
下列 API 從 Compose 1.11.0-beta01 開始提供。
FlexBox
查看 FlexBox 文件:
MediaQuery
當您需要查詢裝置的螢幕大小、指標精確度、鍵盤類型、是否有相機或麥克風,以及其他裝置功能時,請查看 MediaQuery 文件。
Grid
當您需要在網格版面中顯示固定數量的項目時,請查看 Grid 文件:






