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

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

如果應用程式需要深入瞭解大型集合的元素資訊,例如電子郵件用戶端含有電子郵件清單和每封電子郵件的詳細內容,這個模式就特別實用。清單詳細資料也可用於較不重要的路徑,例如將應用程式偏好設定區分為類別清單,以及詳細資料窗格中各類別的偏好設定。

使用 ListDetailPaneScaffold 實作 UI 模式

ListDetailPaneScaffold 是一種可組合項,可簡化應用程式中「清單/詳細資料」模式的實作方式。清單詳細資料 Scaffold 最多可包含三個窗格:清單窗格、詳細資料窗格和選用的額外窗格。Scaffold 會處理螢幕空間計算。如果可用螢幕大小足夠,詳細資料窗格會顯示在清單窗格旁。在小螢幕上,Sscaffold 會自動切換為全螢幕顯示清單或詳細資料窗格。

清單頁面旁邊會顯示詳細資料窗格。
圖 1 如果可用螢幕大小足夠,詳細資料窗格會與清單窗格一起顯示。
選取項目後,詳細資料窗格就會佔據整個畫面。
圖 2. 限制螢幕大小時,詳細資料窗格 (選取項目後) 會佔據整個空間。

宣告依附元件

ListDetailPaneScaffoldMaterial 3 自動調整程式庫的一部分。在應用程式或模組的 build.gradle 檔案中,新增程式庫的依附元件:

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

基本用法

以下說明 ListDetailPaneScaffold 的基本用法:

  1. 將目前從清單中選取的項目儲存在可變動狀態變數中。變數會存放要在詳細資料窗格中顯示的項目。一般而言,建議您使用 null 初始化目前選取的項目,表示您尚未選取任何項目。

    class MyItem(val id: Int) {
        companion object {
            val Saver: Saver<MyItem?, Int> = Saver(
                { it?.id },
                ::MyItem,
            )
        }
    }

    var selectedItem: MyItem? by rememberSaveable(stateSaver = MyItem.Saver) {
        mutableStateOf(null)
    }

  2. 使用 rememberListDetailPaneScaffoldNavigator 建立 ThreePaneScaffoldNavigator 並新增 BackHandler。這個導覽器是用來在清單、詳細資料和其他窗格之間移動,並將狀態提供給 Scaffold。新增的 BackHandler 支援使用系統返回手勢或按鈕返回操作。ListDetailPaneScaffold 返回按鈕的預期行為取決於視窗大小和目前的 Scaffold 值。如果 ListDetailPaneScaffold 可支援使用目前狀態返回,則 canNavigateBack() 會是 true,並啟用 BackHandler

    val navigator = rememberListDetailPaneScaffoldNavigator<Nothing>()
    
    BackHandler(navigator.canNavigateBack()) {
        navigator.navigateBack()
    }

  3. scaffoldState 從您建立的 navigator 傳遞至 ListDetailPaneScaffold 可組合項。

    ListDetailPaneScaffold(
        directive = navigator.scaffoldDirective,
        value = navigator.scaffoldValue,
        // ...
    )

  4. 將清單窗格實作提供給 ListDetailPaneScaffold。請確認實作內容包含回呼引數,以便擷取新選取的項目。觸發此回呼時,請更新 selectedItem 狀態變數,並使用 ThreePaneScaffoldNavigator 顯示詳細資料窗格 (ListDetailPaneScaffoldRole.Detail)。

    ListDetailPaneScaffold(
        directive = navigator.scaffoldDirective,
        value = navigator.scaffoldValue,
        listPane = {
            AnimatedPane(Modifier) {
                MyList(
                    onItemClick = { id ->
                        // Set current item
                        selectedItem = id
                        // Switch focus to detail pane
                        navigator.navigateTo(ListDetailPaneScaffoldRole.Detail)
                    }
                )
            }
        },
        // ...
    )

  5. ListDetailPaneScaffold 中加入詳細資料窗格實作項目。只有在 selectedItem 不是空值時,才會顯示詳細資料內容。

    ListDetailPaneScaffold(
        directive = navigator.scaffoldDirective,
        value = navigator.scaffoldValue,
        listPane =
        // ...
        detailPane = {
            AnimatedPane(Modifier) {
                selectedItem?.let { item ->
                    MyDetails(item)
                }
            }
        },
    )

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

// Currently selected item
var selectedItem: MyItem? by rememberSaveable(stateSaver = MyItem.Saver) {
    mutableStateOf(null)
}

// Create the ListDetailPaneScaffoldState
val navigator = rememberListDetailPaneScaffoldNavigator<Nothing>()

BackHandler(navigator.canNavigateBack()) {
    navigator.navigateBack()
}

ListDetailPaneScaffold(
    directive = navigator.scaffoldDirective,
    value = navigator.scaffoldValue,
    listPane = {
        AnimatedPane(Modifier) {
            MyList(
                onItemClick = { id ->
                    // Set current item
                    selectedItem = id
                    // Display the detail pane
                    navigator.navigateTo(ListDetailPaneScaffoldRole.Detail)
                },
            )
        }
    },
    detailPane = {
        AnimatedPane(Modifier) {
            // Show the detail pane content if selected item is available
            selectedItem?.let { item ->
                MyDetails(item)
            }
        }
    },
)