Informazioni sulle query per i layout adattivi con mediaQuery

Per aggiornare il layout dell'app, hai bisogno di vari tipi di informazioni, come le 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 dinamicamente, devi monitorarle e attivare la ricomposizione quando si verifica un aggiornamento. La funzione mediaQuery astrae i dettagli del recupero delle informazioni e ti consente di concentrarti sulla definizione della condizione per attivare gli aggiornamenti del layout. L'esempio seguente passa al layout TabletopLayout quando la postura del dispositivo pieghevole è su un tavolo:

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

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

Definire una condizione con parametri

Puoi definire una condizione come lambda che viene valutata in UiMediaScope. La funzione mediaQuery valuta la condizione in base allo stato attuale e alle funzionalità del dispositivo. La funzione restituisce un valore booleano, in modo da poter 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 attuale della finestra 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 videocamera è supportata sul dispositivo.
hasMicrophone Boolean 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 postura del dispositivo. La funzione mediaQuery valuta quindi la funzione lambda query con l'oggetto UiMediaScope aggiornato e restituisce un valore booleano. Ad esempio, il seguente snippet sceglie tra TabletopLayout e FlatLayout in base al valore del parametro windowPosture.

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

Prendere una decisione in base alle dimensioni della finestra

Le classi di dimensioni della finestra sono un insieme di punti di interruzione del viewport che ti aiutano a progettare, sviluppare e testare layout adattabili. 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 ha costanti per le soglie delle classi di dimensioni della finestra (Figura 1).

La funzione derivedMediaQuery valuta l'espressione lambda query e racchiude 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 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 postura della finestra

Il parametro windowPosture descrive la postura della finestra corrente come oggetto UiMediaScope.Posture. Puoi controllare la postura attuale 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()
}

Controllare la precisione del dispositivo di puntamento disponibile

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

Il parametro pointerPrecision descrive la precisione dei dispositivi di puntamento disponibili, come mouse e touchscreen. Nella classe UiMediaScope.PointerPrecision sono definiti quattro valori: Fine, Coarse, Blunt e None. None significa che non è disponibile alcun dispositivo di puntamento. La precisione varia dal massimo al minimo nel seguente ordine: Fine, Coarse e Blunt.

Se sono disponibili più dispositivi di puntamento e le loro precisioni sono diverse, il parametro viene risolto con la precisione più elevata. 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 a 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 e contemporaneamente è disponibile una tastiera hardware, il parametro viene risolto come Physical. Se non viene rilevato nessuno dei due, None è il valore del parametro. Il seguente esempio mostra un messaggio che suggerisce agli utenti di collegare una tastiera quando non viene rilevata alcuna tastiera:

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

Controllare se il dispositivo supporta la videocamera e il microfono

Alcuni dispositivi non supportano videocamere o microfoni. Puoi verificare se il dispositivo supporta una videocamera e un microfono con i parametri hasCamera e 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()
    }
}

Regolare l'interfaccia utente in base alla distanza di visualizzazione stimata

La distanza di visualizzazione è un fattore che contribuisce a determinare il layout. Se l'utente utilizza l'app da lontano, si aspetta che il testo e gli elementi dell'interfaccia utente siano più grandi. Il parametro viewingDistance fornisce una stima della distanza di visualizzazione 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 la dimensione 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
}

Visualizzare l'anteprima di un componente UI

Puoi chiamare le funzioni mediaQuery e derivedMediaQuery nelle funzioni componibili per visualizzare l'anteprima dei componenti UI. Il seguente snippet 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 lambda query specificata all'interno di un oggetto UiMediaScope, che viene fornito come LocalUiMediaScope.current. Puoi ignorarlo procedendo nel seguente modo:

  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 componibile per visualizzarlo in anteprima nella 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()
        }
    }
}