Informazioni sulle query per i layout adattivi con mediaQuery

Per aggiornare il layout dell'app, hai bisogno di vari tipi di informazioni, come la funzionalità del dispositivo e lo stato dell'app. La larghezza e l'altezza della finestra sono le informazioni di uso comune. Inoltre, puoi fare riferimento alle seguenti informazioni:

  • Postura della finestra
  • Precisione dei dispositivi di puntamento
  • Tipo di tastiera
  • Se la videocamera e il microfono sono supportati dal dispositivo
  • La distanza tra un utente e il display del dispositivo

Poiché le informazioni vengono aggiornate in modo dinamico, devi monitorarle e attivare la ricomposizione quando si verifica un aggiornamento. La funzione mediaQuery astrae i dettagli del recupero di informazioni e ti consente di concentrarti sulla definizione della condizione per attivare gli aggiornamenti del layout.

L'esempio seguente cambia il layout in TabletopLayout quando la posizione del pieghevole è in modalità tavolo:

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

Attiva la funzione mediaQuery

Per attivare la funzione mediaQuery, imposta l'attributo isMediaQueryIntegrationEnabled dell'oggetto ComposeUiFlags su true:

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

Definisci una condizione con parametri

Puoi definire una condizione come lambda che viene valutata all'interno di UiMediaScope. La funzione mediaQuery valuta la condizione in base allo stato attuale e alle funzionalità del dispositivo. La funzione restituisce un valore booleano, quindi puoi determinare il layout con rami condizionali come un'espressione if. La Tabella 1 descrive i parametri disponibili in UiMediaScope.

Parametro Tipo di valore Descrizione
windowWidth Dp La larghezza della finestra corrente in dp.
windowHeight Dp L'altezza della finestra corrente in dp.
windowPosture UiMediaScope.Posture La postura attuale della finestra dell'applicazione.
pointerPrecision UiMediaScope.PointerPrecision La massima precisione dei dispositivi di puntamento disponibili.
keyboardKind UiMediaScope.KeyboardKind Il tipo di tastiera disponibile o collegata.
hasCamera Boolean Indica se la fotocamera è supportata sul dispositivo.
hasMicrophone Boolean Indica se il microfono è supportato sul dispositivo.
viewingDistance UiMediaScope.ViewingDistance La distanza tipica tra l'utente e lo schermo del dispositivo.

Un oggetto UiMediaScope risolve i valori dei parametri. La funzione mediaQuery utilizza LocalUiMediaScope.current per accedere all'oggetto UiMediaScope, che rappresenta le funzionalità e il contesto del dispositivo corrente. Questo oggetto viene aggiornato dinamicamente quando vengono apportate modifiche, ad esempio quando l'utente cambia la posizione del dispositivo. La funzione mediaQuery valuta quindi la funzione lambda query con l'oggetto UiMediaScope aggiornato e restituisce un valore booleano. Ad esempio, lo snippet seguente sceglie tra TabletopLayout e FlatLayout in base al valore parametro windowPosture.

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

Prendi una decisione in base alle dimensioni della finestra

Le classi di dimensioni delle finestre sono un insieme di breakpoint dei viewport predefiniti che ti aiutano a progettare, sviluppare e testare layout adattivi. Puoi confrontare i due parametri che rappresentano le dimensioni della finestra corrente con la soglia definita nelle classi di dimensioni della finestra. L'esempio seguente modifica il numero di riquadri in base alla larghezza della finestra. La classe WindowSizeClass contiene costanti per le soglie delle classi di dimensioni della finestra (Figura 1).

La funzione derivedMediaQuery valuta la funzione lambda query e aggrega il risultato in un derivedStateOf. Poiché windowWidth e windowHeight possono essere aggiornati di frequente, chiama la funzione derivedMediaQuery anziché la funzione mediaQuery quando fai riferimento a questi parametri nella funzione lambda query.

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

Figura 1. Il layout viene aggiornato in base alla larghezza della finestra.

Aggiorna il layout in base alla posizione della finestra

Il parametro windowPosture descrive la postura corrente della finestra come oggetto UiMediaScope.Posture. Puoi controllare la postura corrente confrontando il parametro con i valori definiti nella classe UiMediaScope.Posture. L'esempio seguente cambia il layout in base alla postura della finestra:

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

Controlla la precisione del dispositivo di puntamento disponibile

Un dispositivo di puntamento ad alta precisione aiuta gli utenti a puntare con precisione un elemento UI. La precisione di un dispositivo di puntamento dipende dal tipo di dispositivo.

Il parametro pointerPrecision descrive la precisione dei dispositivi di puntamento disponibili, come un mouse e un touchscreen. Nella classe UiMediaScope.PointerPrecision sono definiti quattro valori: Fine, Coarse, Blunt e None. None indica che non è disponibile alcun dispositivo di puntamento. La precisione varia dalla più alta alla più bassa in questo ordine: Fine, Coarse e Blunt.

Se sono disponibili più dispositivi di puntamento e le loro precisioni sono diverse, il parametro viene risolto con quella più alta. Ad esempio, se sono presenti due dispositivi di puntamento, un dispositivo di precisione Fine e un dispositivo di precisione Blunt, Fine è il valore del parametro pointerPrecision.

L'esempio seguente mostra un pulsante più grande quando l'utente utilizza un dispositivo di puntamento con bassa precisione:

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

Controlla il tipo di tastiera disponibile

Il parametro keyboardKind rappresenta il tipo di tastiere disponibili: Physical, Virtual e None. Se viene visualizzata una tastiera sullo schermo ed è disponibile contemporaneamente una tastiera hardware, il parametro viene risolto come Physical. Se non viene rilevato nessuno dei due, il valore del parametro è None. L'esempio seguente mostra un messaggio che suggerisce agli utenti di collegare una tastiera quando non ne viene rilevata nessuna:

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

Controlla se il dispositivo supporta la fotocamera e il microfono

Alcuni dispositivi non supportano videocamere o microfoni. Puoi verificare se il dispositivo supporta una videocamera e un microfono con il parametro hasCamera e il parametro hasMicrophone. Il seguente esempio mostra i pulsanti da utilizzare con la videocamera e il microfono quando il dispositivo li supporta:

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

Regola la UI in base alla distanza di visione stimata

La distanza di visione è un fattore che aiuta a determinare il layout. Se l'utente utilizza l'app da lontano, si aspetta che il testo e gli elementi UI siano più grandi. Il parametro viewingDistance fornisce una stima della distanza di visione in base al tipo di dispositivo e al suo contesto di utilizzo tipico.

Nella classe UiMediaScope.ViewingDistance sono definiti tre valori: Near, Medium e Far. Near indica che lo schermo è a distanza ravvicinata, mentre Far indica che il dispositivo viene visualizzato da lontano. L'esempio seguente aumenta le dimensioni del carattere quando la distanza di visualizzazione è Far o Medium:

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

Visualizza l'anteprima di un componente UI

Puoi chiamare le funzioni mediaQuery e derivedMediaQuery nelle funzioni componibili per visualizzare l'anteprima dei componenti della UI. Lo snippet seguente sceglie tra TabletopLayout e FlatLayout in base al valore parametro windowPosture. Per visualizzare l'anteprima di TabletopLayout, il parametro windowPosture deve essere UiMediaScope.Posture.Tabletop.

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

Le funzioni mediaQuery e derivedMediaQuery valutano la funzione lambda query specificata all'interno di un oggetto UiMediaScope, fornito come LocalUiMediaScope.current. Puoi eseguire l'override con i seguenti passaggi:

  1. Attiva la funzione mediaQuery.
  2. Definisci un oggetto personalizzato che implementa l'interfaccia UiMediaScope.
  3. Imposta l'oggetto personalizzato su LocalUiMediaScope con la funzione CompositionLocalProvider.
  4. Chiama il composable per visualizzare l'anteprima nella funzione lambda dei contenuti della funzione CompositionLocalProvider.

Puoi visualizzare l'anteprima di TabletopLayout con il seguente esempio:

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