mediaQuery を使用してアダプティブ レイアウトの情報をクエリする

アプリのレイアウトを更新するには、デバイスの機能やアプリのステータスなど、さまざまな種類の情報が必要です。ウィンドウの幅と高さは、最もよく使用される情報です。また、次の情報も参照してください。

  • ウィンドウの姿勢
  • ポインティング デバイスの精度
  • キーボード タイプ
  • カメラとマイクがデバイスでサポートされているかどうか
  • ユーザーとデバイスのディスプレイ間の距離

情報は動的に更新されるため、更新が発生したときにモニタリングして再コンポーズをトリガーする必要があります。mediaQuery 関数は情報取得の詳細を抽象化し、レイアウトの更新をトリガーする条件の定義に集中できるようにします。

次の例では、折りたたみ式デバイスの姿勢が卓上モードの場合に、レイアウトを TabletopLayout に切り替えます。

@Composable
fun VideoPlayer(
    // ...
) {
    // ...
            if (mediaQuery { windowPosture == UiMediaScope.Posture.Tabletop }) {
                TabletopLayout()
            } else {
                FlatLayout()
            }
    // ...
}

mediaQuery 関数を有効にする

mediaQuery 関数を有効にするには、ComposeUiFlags オブジェクトの isMediaQueryIntegrationEnabled 属性を true に設定します。

class MyApplication : Application() {
    override fun onCreate() {
        ComposeUiFlags.isMediaQueryIntegrationEnabled = true
        super.onCreate()
    }
}

パラメータを使用して条件を定義する

条件は、UiMediaScope 内で評価されるラムダとして定義できます。mediaQuery 関数は、現在のステータスとデバイスの機能に基づいて条件を評価します。この関数はブール値を返すため、if 式のような条件分岐でレイアウトを決定できます。表 1 に、UiMediaScope で使用できるパラメータを示します。

パラメータ 値の型 説明
windowWidth Dp 現在のウィンドウの幅(dp 単位)。
windowHeight Dp 現在のウィンドウの高さ(dp 単位)。
windowPosture UiMediaScope.Posture アプリケーション ウィンドウの現在のポスチャー。
pointerPrecision UiMediaScope.PointerPrecision 利用可能なポインティング デバイスの最高精度。
keyboardKind UiMediaScope.KeyboardKind 利用可能または接続されているキーボードのタイプ。
hasCamera Boolean デバイスでカメラがサポートされているかどうか。
hasMicrophone Boolean デバイスでマイクがサポートされているかどうか。
viewingDistance UiMediaScope.ViewingDistance ユーザーとデバイスの画面の間の一般的な距離。

UiMediaScope オブジェクトは、パラメータの値を解決します。mediaQuery 関数は LocalUiMediaScope.current を使用して、現在のデバイスの機能とコンテキストを表す UiMediaScope オブジェクトにアクセスします。このオブジェクトは、ユーザーがデバイスのポーズを変更した場合など、変更が行われると動的に更新されます。次に、mediaQuery 関数は更新された UiMediaScope オブジェクトを使用して query ラムダを評価し、ブール値を返します。たとえば、次のスニペットでは、windowPosture パラメータの値に基づいて TabletopLayoutFlatLayout のいずれかを選択します。

@Composable
fun VideoPlayer(
    // ...
) {
    // ...
            if (mediaQuery { windowPosture == UiMediaScope.Posture.Tabletop }) {
                TabletopLayout()
            } else {
                FlatLayout()
            }
    // ...
}

ウィンドウ サイズに基づいて決定する

ウィンドウ サイズクラスは、アダプティブ レイアウトの設計、開発、テストに役立つ独自のビューポート ブレークポイントのセットです。現在のウィンドウ サイズを表す 2 つのパラメータを、ウィンドウ サイズクラスで定義されたしきい値と比較できます。次の例では、ウィンドウの幅に応じてペインの数を変更します。WindowSizeClass クラスには、ウィンドウ サイズクラスのしきい値の定数があります(図 1)。

derivedMediaQuery 関数は query ラムダを評価し、結果を derivedStateOf でラップします。windowWidthwindowHeight は頻繁に更新される可能性があるため、query ラムダでこれらのパラメータを参照する場合は、mediaQuery 関数ではなく derivedMediaQuery 関数を呼び出します。

val narrowerThanMedium by derivedMediaQuery {
    windowWidth < WindowSizeClass.WIDTH_DP_MEDIUM_LOWER_BOUND.dp
}
val narrowerThanExpanded by derivedMediaQuery {
    windowWidth < WindowSizeClass.WIDTH_DP_EXPANDED_LOWER_BOUND.dp
}
when {
    narrowerThanMedium -> SinglePaneLayout()
    narrowerThanExpanded -> TwoPaneLayout()
    else -> ThreePaneLayout()
}

図 1. ウィンドウ幅に応じてレイアウトが更新される。

ウィンドウのポーズに応じてレイアウトを更新する

windowPosture パラメータは、現在のウィンドウのポーズを UiMediaScope.Posture オブジェクトとして記述します。パラメータを UiMediaScope.Posture クラスで定義されている値と比較することで、現在の姿勢を確認できます。次の例では、ウィンドウのポーズに応じてレイアウトを切り替えます。

when {
    mediaQuery { windowPosture == UiMediaScope.Posture.Tabletop } -> TabletopLayout()
    mediaQuery { windowPosture == UiMediaScope.Posture.Book } -> BookLayout()
    mediaQuery { windowPosture == UiMediaScope.Posture.Flat } -> FlatLayout()
}

利用可能なポインティング デバイスの精度を確認する

高精度のポインティング デバイスは、ユーザーが UI 要素を正確にポイントするのに役立ちます。ポインティング デバイスの精度は、デバイスの種類によって異なります。

pointerPrecision パラメータは、マウスやタッチスクリーンなどの利用可能なポインティング デバイスの精度を表します。UiMediaScope.PointerPrecision クラスには、FineCoarseBluntNone の 4 つの値が定義されています。None は、ポインティング デバイスが利用できないことを意味します。精度は、FineCoarseBlunt の順に高くなります。

複数のポインティング デバイスが利用可能で、それらの精度が異なる場合、パラメータは最も高い精度で解決されます。たとえば、Fine 精密デバイスと Blunt 精密デバイスの 2 つのポインティング デバイスがある場合、pointerPrecision パラメータの値は Fine です。

次の例は、ユーザーが精度の低いポインティング デバイスを使用している場合に、ボタンを大きく表示する方法を示しています。

if (mediaQuery { pointerPrecision == UiMediaScope.PointerPrecision.Blunt }) {
    LargeSizeButton()
} else {
    NormalSizeButton()
}

利用可能なキーボードの種類を確認する

keyboardKind パラメータは、利用可能なキーボードのタイプ(PhysicalVirtualNone)を表します。画面キーボードが表示され、同時にハードウェア キーボードが利用可能な場合、パラメータは Physical として解決されます。どちらも検出されない場合、None がパラメータの値になります。次の例は、キーボードが検出されなかった場合に、キーボードを接続するようユーザーに促すメッセージを示しています。

if (mediaQuery { keyboardKind == UiMediaScope.KeyboardKind.None }) {
    SuggestKeyboardConnect()
}

デバイスがカメラとマイクをサポートしているかどうかを確認する

一部のデバイスはカメラやマイクに対応していません。hasCamera パラメータと hasMicrophone パラメータを使用して、デバイスがカメラとマイクをサポートしているかどうかを確認できます。次の例は、デバイスがカメラとマイクをサポートしている場合に使用するボタンを示しています。

Row {
    OutlinedTextField(state = rememberTextFieldState())
    // Show the MicButton when the device supports a microphone.
    if (mediaQuery { hasMicrophone }) {
        MicButton()
    }
    // Show the CameraButton when the device supports a camera.
    if (mediaQuery { hasCamera }) {
        CameraButton()
    }
}

推定視聴距離に基づいて UI を調整

視聴距離はレイアウトを決定する要因です。ユーザーがアプリを離れた場所から使用している場合、テキストや UI 要素が大きくなることを期待します。viewingDistance パラメータは、デバイスの種類とその一般的な使用状況に基づいて、視聴距離の推定値を提供します。

UiMediaScope.ViewingDistance クラスには、NearMediumFar の 3 つの値が定義されています。Near は画面が近いことを意味し、Far はデバイスが遠くから見られていることを意味します。次の例では、視聴距離が Far または Medium の場合にフォントサイズを大きくします。

val fontSize = when {
    mediaQuery { viewingDistance == UiMediaScope.ViewingDistance.Far } -> 20.sp
    mediaQuery { viewingDistance == UiMediaScope.ViewingDistance.Medium } -> 18.sp
    else -> 16.sp
}

UI コンポーネントをプレビューする

コンポーズ可能な関数で mediaQuery 関数と derivedMediaQuery 関数を呼び出して、UI コンポーネントをプレビューできます。次のスニペットは、windowPosture パラメータ値に基づいて TabletopLayoutFlatLayout のいずれかを選択します。TabletopLayout をプレビューするには、windowPosture パラメータを UiMediaScope.Posture.Tabletop にする必要があります。

when {
    mediaQuery { windowPosture == UiMediaScope.Posture.Tabletop } -> TabletopLayout()
    mediaQuery { windowPosture == UiMediaScope.Posture.Book } -> BookLayout()
    mediaQuery { windowPosture == UiMediaScope.Posture.Flat } -> FlatLayout()
}

mediaQuery 関数と derivedMediaQuery 関数は、指定された query ラムダを UiMediaScope オブジェクト内で評価します。このオブジェクトは LocalUiMediaScope.current として提供されます。オーバーライドするには、次の手順を行います。

  1. mediaQuery 関数を有効にします。
  2. UiMediaScope インターフェースを実装するカスタム オブジェクトを定義します。
  3. CompositionLocalProvider 関数を使用して、カスタム オブジェクトを LocalUiMediaScope に設定します。
  4. CompositionLocalProvider 関数のコンテンツ ラムダで、プレビューするコンポーザブルを呼び出します。

次の例で TabletopLayout をプレビューできます。

@Preview
@Composable
fun PreviewLayoutForTabletop() {
    // Step 1: Enable the mediaQuery function
    ComposeUiFlags.isMediaQueryIntegrationEnabled = true

    val currentUiMediaScope = LocalUiMediaScope.current
    // Step 2: Define a custom object implementing the UiMediaScope interface.
    // The object overrides the windowPosture parameter.
    // The resolution of the remaining parameters is deferred to the currentUiMediaScope object.
    val uiMediaScope = remember(currentUiMediaScope) {
        object : UiMediaScope by currentUiMediaScope {
            override val windowPosture: UiMediaScope.Posture = UiMediaScope.Posture.Tabletop
        }
    }

    // Step 3: Set the object to the LocalUiMediaScope.
    CompositionLocalProvider(LocalUiMediaScope provides uiMediaScope) {
        // Step 4: Call the composable to preview.
        when {
            mediaQuery { windowPosture == UiMediaScope.Posture.Tabletop } -> TabletopLayout()
            mediaQuery { windowPosture == UiMediaScope.Posture.Book } -> BookLayout()
            mediaQuery { windowPosture == UiMediaScope.Posture.Flat } -> FlatLayout()
        }
    }
}