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 パラメータ値に基づいて TabletopLayout と FlatLayout のいずれかを選択します。

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

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

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

derivedMediaQuery 関数は、query ラムダを評価し、結果を derivedStateOf でラップします。windowWidth と windowHeight は頻繁に更新される可能性があるため、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 クラスには、Fine、Coarse、Blunt、None の 4 つの値が定義されています。None は、ポインティング デバイスが利用できないことを意味します。精度は、Fine、Coarse、Blunt の順に高くなります。

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

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

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

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

keyboardKind パラメータは、使用可能なキーボードのタイプ(Physical、Virtual、None)を表します。画面キーボードが表示され、同時にハードウェア キーボードが使用可能な場合、パラメータは 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 クラスには、Near、Medium、Far の 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 パラメータ値に基づいて TabletopLayout と FlatLayout のいずれかを選択します。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 関数は、LocalUiMediaScope.current として提供される UiMediaScope オブジェクト内で、指定された query ラムダを評価します。これをオーバーライドする手順は次のとおりです。

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