Información de la consulta para diseños adaptables con mediaQuery

Necesitas varios tipos de información, como la capacidad del dispositivo y el estado de la app, para actualizar el diseño de tu app. El ancho y la altura de la ventana son la información que se usa con más frecuencia. Además, puedes consultar la siguiente información:

  • Posición de la ventana
  • Precisión de los dispositivos de puntero
  • Tipo de teclado
  • Si el dispositivo admite la cámara y el micrófono
  • La distancia entre un usuario y la pantalla del dispositivo

Debido a que la información se actualiza de forma dinámica, debes supervisarla y activar la recomposición cuando se produzca alguna actualización. La función mediaQuery abstrae los detalles de la recuperación de información y te permite enfocarte en definir la condición para activar las actualizaciones de diseño. En el siguiente ejemplo, se cambia el diseño a TabletopLayout cuando la posición plegable es de mesa:

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

Habilita la función mediaQuery

Para habilitar la función mediaQuery, establece el atributo isMediaQueryIntegrationEnabled de el objeto ComposeUiFlags en true:

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

Define una condición con parámetros

Puedes definir una condición como una lambda que se evalúa dentro de UiMediaScope. La función mediaQuery evalúa la condición según el estado actual y las capacidades del dispositivo. La función muestra un valor booleano, por lo que puedes determinar el diseño con ramas condicionales, como una expresión if. En la tabla 1, se describen los parámetros disponibles en UiMediaScope.

Parámetro Tipo de valor Descripción
windowWidth Dp Es el ancho actual de la ventana en dp.
windowHeight Dp Es la altura actual de la ventana en dp.
windowPosture UiMediaScope.Posture Es la posición actual de la ventana de la aplicación.
pointerPrecision UiMediaScope.PointerPrecision Es la precisión más alta de los dispositivos de puntero disponibles.
keyboardKind UiMediaScope.KeyboardKind Es el tipo de teclado disponible o conectado.
hasCamera Boolean Indica si la cámara es compatible con el dispositivo.
hasMicrophone Boolean Indica si el micrófono es compatible con el dispositivo.
viewingDistance UiMediaScope.ViewingDistance Es la distancia típica entre el usuario y la pantalla del dispositivo.

Un objeto UiMediaScope resuelve los valores de los parámetros. La función mediaQuery usa LocalUiMediaScope.current para acceder al objeto UiMediaScope, que representa las capacidades y el contexto actuales del dispositivo. Este objeto se actualiza de forma dinámica cuando se realizan cambios, como cuando el usuario cambia la posición del dispositivo. Luego, la función mediaQuery evalúa la lambda query con el objeto UiMediaScope actualizado y muestra un valor booleano. Por ejemplo, el siguiente fragmento elige entre TabletopLayout y FlatLayout según el valor del parámetro windowPosture.

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

Toma una decisión según el tamaño de la ventana

Las clases de tamaño de ventana son un conjunto de puntos de interrupción de viewports bien definidos que te ayudan a diseñar, desarrollar y probar diseños adaptables. Puedes comparar los dos parámetros que representan el tamaño actual de la ventana con el umbral definido en las clases de tamaño de ventana. En el siguiente ejemplo, se cambia la cantidad de paneles según el ancho de la ventana. WindowSizeClass clase tiene constantes para los umbrales de las clases de tamaño de ventana (Figura 1).

La derivedMediaQuery función evalúa la query lambda y ajusta el resultado en un derivedStateOf. Debido a que windowWidth y windowHeight se pueden actualizar con frecuencia, llama a la función derivedMediaQuery en lugar de la función mediaQuery cuando hagas referencia a esos parámetros en la 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 : El diseño se actualiza según el ancho de la ventana.

Actualiza el diseño según la posición de la ventana

El parámetro windowPosture describe la posición actual de la ventana como un objeto UiMediaScope.Posture. Puedes verificar la posición actual comparando el parámetro con los valores definidos en la UiMediaScope.Posture clase. En el siguiente ejemplo, se cambia el diseño según la posición de la ventana:

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

Verifica la precisión del dispositivo de puntero disponible

Un dispositivo de puntero de alta precisión ayuda a los usuarios a señalar un elemento de la IU con precisión. La precisión de un dispositivo de puntero depende del tipo de dispositivo.

El parámetro pointerPrecision describe la precisión de los dispositivos de puntero disponibles, como un mouse y una pantalla táctil. Hay cuatro valores definidos en la clase UiMediaScope.PointerPrecision: Fine, Coarse, Blunt y None. None significa que no hay ningún dispositivo de puntero disponible. La precisión varía de mayor a menor en este orden: Fine, Coarse y Blunt.

Si hay varios dispositivos de puntero disponibles y sus precisiones son diferentes, el parámetro se resuelve con el más alto. Por ejemplo, si hay dos dispositivos de puntero (un dispositivo de precisión Fine y un dispositivo de precisión Blunt), Fine es el valor del parámetro pointerPrecision.

En el siguiente ejemplo, se muestra un botón más grande cuando el usuario usa un dispositivo de puntero con baja precisión:

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

Verifica el tipo de teclado disponible

El parámetro keyboardKind representa el tipo de teclados disponibles: Physical, Virtual y None. Si se muestra un teclado en pantalla y un teclado de hardware está disponible al mismo tiempo, el parámetro se resuelve como Physical. Si no se detecta ninguno, None es el valor del parámetro. En el siguiente ejemplo, se muestra un mensaje que sugiere que los usuarios conecten un teclado cuando no se detecta ninguno:

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

Verifica si el dispositivo admite la cámara y el micrófono

Algunos dispositivos no admiten cámaras ni micrófonos. Puedes verificar si el dispositivo admite una cámara y un micrófono con el parámetro hasCamera y el parámetro hasMicrophone. En el siguiente ejemplo, se muestran botones para usar con la cámara y el micrófono cuando el dispositivo los admite:

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

Ajusta la IU con la distancia de visualización estimada

La distancia de visualización es un factor que ayuda a determinar el diseño. Si el usuario usa la app desde una distancia, esperará que el texto y los elementos de la IU sean más grandes. El parámetro viewingDistance proporciona una estimación de la distancia de visualización según el tipo de dispositivo y su contexto de uso típico.

Hay tres valores definidos en la clase UiMediaScope.ViewingDistance: Near, Medium y Far. Near significa que la pantalla está cerca, y Far significa que el dispositivo se ve desde una distancia. En el siguiente ejemplo, se aumenta el tamaño de la fuente cuando la distancia de visualización es Far o Medium:

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

Obtén una vista previa de un componente de la IU

Puedes llamar a las funciones mediaQuery y derivedMediaQuery en las funciones de componibilidad para obtener una vista previa de los componentes de la IU. El siguiente fragmento elige entre TabletopLayout y FlatLayout según el valor del parámetro windowPosture. Para obtener una vista previa de TabletopLayout, el parámetro windowPosture debe ser UiMediaScope.Posture.Tabletop.

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

Las funciones mediaQuery y derivedMediaQuery evalúan la lambda query determinada dentro de un objeto UiMediaScope, que se proporciona como LocalUiMediaScope.current. Puedes anularlo con los siguientes pasos:

  1. Habilita la función mediaQuery.
  2. Define un objeto personalizado que implemente la interfaz UiMediaScope.
  3. Establece el objeto personalizado en el LocalUiMediaScope con la CompositionLocalProvider función.
  4. Llama a la función de componibilidad para obtener una vista previa en la lambda de contenido de la función CompositionLocalProvider.

Puedes obtener una vista previa de TabletopLayout con el siguiente ejemplo:

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