Grid と mediaQuery を使用した卓上姿勢レイアウト

4 つ星の評価アイコン

折りたたみ式デバイスのテーブルトップ形状では、1 つの画面が物理的な折り目(またはヒンジ)で区切られた 2 つの機能的な表示領域に分割されます。 テーブルトップ形状では、アプリのメイン コンテンツ(動画プレーヤーやゲーム キャンバスなど)を上半分に配置し、インタラクティブな要素(メディア コントロールやコントロール パッドなど)を下半分に配置できます。

テーブルトップ姿勢の折りたたみ式デバイス。上部の画面に動画コンテンツが表示され、下部の画面に再生コントロールが表示されている。
図 1.テーブルトップ形状では、コンテンツとインタラクティブなコントロールが物理的な折り目をまたいで分割されます。

ベスト プラクティス

アプリにテーブルトップのサポートを追加するには、Grid API を使用して 2 行 1 列のレイアウトを定義し、テーブルトップ ウィンドウ形状のメディアクエリを適用します。

  • テーブルトップ形状をリアクティブに検出する: mediaQuery コンポーザブルを使用して、デバイスの状態を監視します。デバイスがテーブルトップ形状に折りたたまれた場合(windowPosture == Posture.Tabletop)、アクティビティの再起動や複雑なライフサイクル処理を必要とせずに、レイアウトを動的に変更します。
  • `Grid` でレイアウトを構造化する Grid: テーブルトップ形状の場合は、Grid API(2 rows、1 column)を使用して、主要なビジュアル コンテンツをスクロールせずに見える範囲の上段に、インタラクティブなコントロールをスクロールしなければ見えない範囲の下段に明確に分離します。
  • オーバーレイに暗黙的な z インデックスを使用する: 標準(テーブルトップ以外)の形状では、同じセルに複数の領域 ID を割り当てて、プレーヤーの上にコントロールを重ねます。Grid は、宣言された順にアイテムを奥から手前にレンダリングします。
  • スムーズなトランジションを提供する: 標準の全画面レイアウトとテーブルトップ形状を切り替える際に、状態とスクロール位置が保持されるようにします。

材料

  • Grid: 構成可能な行と列にアイテムを配置できるアダプティブ レイアウト コンテナ。画面の異なる領域にコンテンツを分割するのに最適です。
  • mediaQuery: コンポーザブル内でアプリの現在のウィンドウ形状、サイズクラス、ハードウェア特性を直接クエリするために使用される Jetpack Compose API。

手順

mediaQuery() を使用してデバイスのポスチャーをクエリします。デバイスがテーブルトップ形状の場合は、アプリのコンテンツを 2 行 1 列の垂直グリッドに配置し、主要なコンテンツが上部に、コントロールが下部に配置されるようにします。

1. テーブルトップ モードのグリッド構成を定義する

コンポーズ可能な関数内で、列挙型を使用してレイアウト領域 ID を定義し、デフォルト形状とテーブルトップ形状の GridConfigurationScope ブロックを設定します。

defaultConfig では、両方の領域が同じセル(row = 1, column = 1)に割り当てられます。Grid は暗黙的な z インデックスを使用します。レイアウトで後から宣言されたコンポーザブルが先に宣言されたコンポーザブルの上にレンダリングされるため、コントローラは追加のコンテナのネストなしでプレーヤーをオーバーレイできます(詳しくは、グリッドを使用した UI 要素のオーバーレイをご覧ください)。

enum class VideoPlayerArea { Player, Controller }

object PlayerGridConfig {
    // Default: Single area layout with overlay (controls on top of player)
    val defaultConfig: GridConfigurationScope.() -> Unit = {
        row(1f)
        column(1f)
        area(areaId = VideoPlayerArea.Player, row = 1, column = 1)
        area(areaId = VideoPlayerArea.Controller, row = 1, column = 1)
    }

    // Tabletop: Two rows splitting content across the fold
    val tabletopConfig: GridConfigurationScope.() -> Unit = {
        row(0.5f)
        row(0.5f)
        column(1f)
        area(areaId = VideoPlayerArea.Player, row = 1, column = 1) // Top half above fold
        area(areaId = VideoPlayerArea.Controller, row = 2, column = 1) // Bottom half below fold
    }
}

2. テーブルトップのポスチャーを検出し、構成をグリッドに適用する

mediaQuery を使用して windowPosture に基づいてアクティブな構成を選択し、config を Grid に渡して、コンポーザブル コンテンツを領域 ID にバインドします。VideoPlayerArea.Controller の前に VideoPlayerArea.Player を宣言すると、両方が defaultConfig でセルを共有する場合に、コントロールがプレーヤーの上にレンダリングされます。

val config = mediaQuery {
    when (windowPosture) {
        Posture.Tabletop -> PlayerGridConfig.tabletopConfig
        else -> PlayerGridConfig.defaultConfig
    }
}

Grid(config = config) {
    Box(
        modifier = Modifier
            .gridItem(areaId = VideoPlayerArea.Player)
    ) {
        // VideoPlayerContent
    }

    Box(
        modifier = Modifier
            .gridItem(areaId = VideoPlayerArea.Controller)
    ) {
        // PlaybackControlsContent
    }
}

結果

mediaQuery と Grid を組み合わせることで、折りたたみ式デバイスがテーブルトップ形状に部分的に折りたたまれたときにアプリが動的に検出し、折り目で区切られた 2 つの機能的な表示領域に UI を自動的に調整します。

参考情報