使用 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 中評估的 lambda。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 lambda,並傳回布林值。舉例來說,下列程式碼片段會根據 windowPosture 參數值,在 TabletopLayout 和 FlatLayout 之間做出選擇。

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

根據視窗大小做出決定

視窗大小類別是一組固定的可視區域中斷點,可協助您設計、開發及測試自動調整式版面配置。您可以比較代表目前視窗大小的兩個參數,以及視窗大小類別中定義的門檻。以下範例會根據視窗寬度變更窗格數量。WindowSizeClass 類別具有視窗大小類別門檻的常數 (圖 1)。

derivedMediaQuery 函式會評估 query lambda,並將結果包裝在 derivedStateOf 中。由於 windowWidth 和 windowHeight 可能會頻繁更新,因此在 query lambda 中參照這些參數時,請呼叫 derivedMediaQuery 函式,而非 mediaQuery 函式。

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。None 表示沒有可用的指標裝置。精確度由高到低依序為:Fine、Coarse 和 Blunt。

如果有多個指標裝置,且精確度不同,系統會以最高精確度解析參數。舉例來說,如果有兩個指標裝置 (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。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 函式會評估 UiMediaScope 物件 (以 LocalUiMediaScope.current 形式提供) 內的指定 query lambda。您可以按照下列步驟覆寫:

  1. 啟用 mediaQuery 函式。
  2. 定義實作 UiMediaScope 介面的自訂物件。
  3. 使用 CompositionLocalProvider 函式將自訂物件設為 LocalUiMediaScope。
  4. 在 CompositionLocalProvider 函式的內容 lambda 中,呼叫要預覽的可組合項。

您可以透過以下範例預覽 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()
        }
    }
}