「リストと詳細」レイアウトを作成する

リスト詳細は、デュアルペイン レイアウトで構成される UI パターンです。1 つのペインにはアイテムのリストが表示され、別のペインにはリストから選択されたアイテムの詳細が表示されます。

このパターンは、大規模なコレクションの要素に関する詳細情報を提供するアプリケーションに特に便利です。たとえば、メールのリストと各メール メッセージの詳細なコンテンツがあるメール クライアントなどです。リスト / 詳細は、重要度の低いパスにも使用できます。たとえば、アプリの設定をカテゴリのリストに分割し、詳細ペインに各カテゴリの設定を表示する場合などです。

ListDetailPaneScaffold で UI パターンを実装する

ListDetailPaneScaffold は、アプリでのリスト詳細パターンの実装を簡素化するコンポーザブルです。リストと詳細のスキャフォールドは最大 3 つのペイン(リストペイン、詳細ペイン、オプションの追加ペイン)で構成できます。スキャフォールドは画面スペースの計算を処理します。十分な画面サイズを使用できる場合、詳細ペインがリストペインと並んで表示されます。小さい画面サイズでは、スキャフォールドが自動的に切り替わり、リストペインまたは詳細ペインが全画面表示されます。

リストページの横に表示される詳細ペイン。
図 1. 十分な画面サイズを使用できる場合、詳細ペインがリストペインと並んで表示されます。
項目を選択すると、詳細ペインが画面全体に表示されます。
図 2. 画面サイズが限られている場合は、アイテムが選択されているため、詳細ペインがスペース全体に表示されます。

依存関係の宣言

ListDetailPaneScaffold は、マテリアル 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 を追加します。このナビゲータは、リスト、詳細、追加ペイン間を移動し、スキャフォールドに状態を提供するために使用されます。追加された BackHandler は、システムの「戻る」ジェスチャーまたはボタンを使用して戻る操作をサポートしています。ListDetailPaneScaffold の [戻る] ボタンの想定される動作は、ウィンドウ サイズと現在のスキャフォールド値によって異なります。ListDetailPaneScaffold が現在の状態への戻ることをサポートできる場合、canNavigateBack()true になり、BackHandler が有効になります。

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

  3. 作成した navigator から scaffoldStateListDetailPaneScaffold コンポーザブルに渡します。

    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 が null でない場合にのみ、詳細コンテンツを表示します。

    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)
            }
        }
    },
)