建構清單/詳細資料版面配置

清單/詳細資料是一種 UI 模式,由雙窗格版面配置組成,其中一個窗格會顯示項目清單,另一個窗格則會顯示從清單中選取項目的詳細資料。

這個模式特別適合用來提供大型集合元素深入資訊的應用程式,例如電子郵件用戶端 (內含電子郵件清單和每封郵件的詳細內容)。清單詳細資料也可以用於較不重要的路徑,例如將應用程式偏好設定劃分為類別清單,並在詳細資料窗格中顯示每個類別的偏好設定。

清單頁面旁會顯示詳細資料窗格。
圖 1. 當螢幕大小足夠時,詳細資料窗格會與清單窗格並排顯示。
選取項目後,詳細資料窗格會占滿整個畫面。
圖 2. 螢幕大小受限時,詳細資料窗格 (因為已選取項目) 會佔用整個空間。

使用「NavigableListDetailPaneScaffold」實作清單/詳細資料模式

NavigableListDetailPaneScaffold 是一個可組合函式,可簡化在 Jetpack Compose 中實作清單詳細資料版面配置的作業。這個函式會包裝 ListDetailPaneScaffold,並新增內建導覽和預測返回動畫。

清單詳細資料支架最多可支援三個窗格:

  1. 清單窗格:顯示項目集合。
  2. 詳細資料窗格:顯示所選項目的詳細資料。
  3. 額外窗格 (選用):視需要提供額外背景資訊。

支架會根據視窗大小調整:

  • 在大視窗中,清單和詳細資料窗格會並排顯示。
  • 在小視窗中,一次只會顯示一個窗格,並隨著使用者瀏覽而切換。

宣告依附元件

NavigableListDetailPaneScaffold 屬於 Material 3 自動調整式導覽程式庫

在應用程式或模組的 build.gradle 檔案中,新增下列三個相關依附元件:

Kotlin

implementation("androidx.compose.material3.adaptive:adaptive")
implementation("androidx.compose.material3.adaptive:adaptive-layout")
implementation("androidx.compose.material3.adaptive:adaptive-navigation")

Groovy

implementation 'androidx.compose.material3.adaptive:adaptive'
implementation 'androidx.compose.material3.adaptive:adaptive-layout'
implementation 'androidx.compose.material3.adaptive:adaptive-navigation'
  • 自動調整:低階建構區塊,例如 HingeInfoPosture
  • 自動調整式版面配置:例如 ListDetailPaneScaffoldSupportingPaneScaffold
  • adaptive-navigation:用於在窗格內和窗格之間導覽的可組合項,以及預設支援導覽的自動調整式版面配置,例如 NavigableListDetailPaneScaffoldNavigableSupportingPaneScaffold

確認專案包含 compose-material3-adaptive 1.1.0-beta1 以上版本

選擇啟用預測返回手勢

如要在 Android 15 以下版本中啟用預測返回動畫,您必須選擇支援預測返回手勢。如要選擇採用,請將 android:enableOnBackInvokedCallback="true" 新增至 AndroidManifest.xml 檔案中的 <application> 標記或個別 <activity> 標記。詳情請參閱「選擇啟用預測返回手勢」。

應用程式指定 Android 16 (API 級別 36) 以上版本後,預測返回功能就會預設為啟用。

基本用法

實作 NavigableListDetailPaneScaffold,如下所示:

  1. 使用代表所選內容的類別。使用 Parcelable 類別,支援儲存及還原所選清單項目。使用 kotlin-parcelize 外掛程式產生程式碼。
  2. 使用 rememberListDetailPaneScaffoldNavigator 建立 ThreePaneScaffoldNavigator

這個導覽器用於在清單、詳細資料和額外窗格之間移動。宣告泛型後,導覽器也會追蹤 Scaffold 的狀態 (也就是顯示哪個 MyItem)。由於這個型別可封送,因此導覽器可以儲存及還原狀態,自動處理設定變更。

  1. 將導覽器傳遞至 NavigableListDetailPaneScaffold 可組合函式。

  2. 將清單窗格實作項目提供給 NavigableListDetailPaneScaffold。使用 AnimatedPane 在導覽期間套用預設窗格動畫。然後使用 ThreePaneScaffoldNavigator 導覽至詳細資料窗格 ListDetailPaneScaffoldRole.Detail,並顯示傳遞的項目。

  3. NavigableListDetailPaneScaffold 中加入詳細資料窗格實作項目。

導覽完成後,currentDestination 會包含應用程式導覽至的窗格,包括窗格中顯示的內容。contentKey 屬性與原始呼叫中指定的型別相同,因此您可以存取需要顯示的任何資料。

  1. 您也可以選擇變更 NavigableListDetailPaneScaffold 中的 defaultBackBehavior。根據預設,NavigableListDetailPaneScaffold 會使用 PopUntilScaffoldValueChange 做為 defaultBackBehavior

如果應用程式需要不同的返回導覽模式,您可以指定其他 BackNavigationBehavior 選項,覆寫此行為。

BackNavigationBehavior 種付款方式

以下章節以電子郵件應用程式為例,說明如何在一側窗格中顯示電子郵件清單,另一側則顯示詳細檢視畫面。

這項行為的重點是整體版面配置結構的變化。在多窗格設定中,變更詳細資料窗格中的電子郵件內容不會改變基礎版面配置結構。因此,如果目前情境中沒有可還原的版面配置變更,「返回」按鈕可能會離開應用程式或目前的導覽圖。在單一窗格版面配置中,按下返回鍵會略過詳細資料檢視畫面中的內容變更,並返回清單檢視畫面,因為這代表版面配置已明確變更。

請見以下範例:

  • 多窗格:您正在詳細資料窗格中查看電子郵件 (項目 1)。按一下另一封電子郵件 (項目 2) 會更新詳細資料窗格,但清單和詳細資料窗格仍會顯示。按下返回鍵可能會退出應用程式或目前的導覽流程。
  • 單一窗格:查看項目 1,然後查看項目 2,按下返回鍵會直接返回電子郵件清單窗格。

如果希望使用者在每次返回操作時,都能感受到明顯的版面配置轉場效果,請使用這個方法。

導覽值變更。
PopUntilContentChange

這項行為會優先顯示內容。如果您先查看項目 1,再查看項目 2,無論版面配置為何,按下返回鍵都會返回項目 1。

請見以下範例:

  • 多窗格:在詳細資料窗格中查看項目 1,然後按一下清單中的項目 2。詳細資料窗格會更新。按下返回鍵會將詳細資料窗格還原為項目 1。
  • 單一窗格:系統會還原為相同內容。

如果使用者預期透過返回動作返回先前查看的內容,請使用這項功能。

兩個詳細資料窗格之間的轉場效果
PopUntilCurrentDestinationChange

這項行為會彈出返回堆疊,直到目前導覽目的地變更為止。這項原則同樣適用於單一和多窗格版面配置。

請見以下範例:

無論您是使用單一或多窗格版面配置,按下返回鍵一律會將焦點從醒目顯示的導覽元素移至上一個目的地。在電子郵件應用程式中,這表示所選窗格的視覺指標會移動。

如果維持目前導覽的清楚視覺指標對使用者體驗至關重要,請使用這個屬性。

在詳細資料窗格和清單窗格之間切換
PopLatest

這個選項只會從返回堆疊中移除最近的目的地。使用這個選項進行返回導覽,不會略過中間狀態。

完成這些步驟後,您的程式碼應如下所示:

val scaffoldNavigator = rememberListDetailPaneScaffoldNavigator<MyItem>()
val scope = rememberCoroutineScope()

NavigableListDetailPaneScaffold(
    navigator = scaffoldNavigator,
    listPane = {
        AnimatedPane {
            MyList(
                onItemClick = { item ->
                    // Navigate to the detail pane with the passed item
                    scope.launch {
                        scaffoldNavigator.navigateTo(
                            ListDetailPaneScaffoldRole.Detail,
                            item
                        )
                    }
                },
            )
        }
    },
    detailPane = {
        AnimatedPane {
            // Show the detail pane content if selected item is available
            scaffoldNavigator.currentDestination?.contentKey?.let {
                MyDetails(it)
            }
        }
    },
)