Consultar informações para layouts adaptáveis com mediaQuery

Você precisa de vários tipos de informações, como a capacidade do dispositivo e o status do app, para atualizar o layout. A largura e a altura da janela são as informações mais usadas com frequência. Além disso, você pode consultar as seguintes informações:

  • Posição da janela
  • Precisão dos dispositivos apontadores
  • Tipo de teclado
  • Se a câmera e o microfone são compatíveis com o dispositivo
  • A distância entre um usuário e a tela do dispositivo

Como as informações são atualizadas dinamicamente, é necessário monitorá-las e acionar a recomposição quando houver uma atualização. A função mediaQuery abstrai os detalhes da recuperação de informações e permite que você se concentre na definição da condição para acionar as atualizações de layout. O exemplo a seguir muda o layout para TabletopLayout quando a posição dobrável é de mesa:

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

Ativar a função mediaQuery

Para ativar a função mediaQuery, defina o atributo isMediaQueryIntegrationEnabled de o objeto ComposeUiFlags como true:

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

Definir uma condição com parâmetros

É possível definir uma condição como uma lambda avaliada em UiMediaScope. A função mediaQuery avalia a condição de acordo com o status atual e as capacidades do dispositivo. A função retorna um valor booleano, então você pode determinar o layout com ramificações condicionais, como uma expressão if. A tabela 1 descreve os parâmetros disponíveis em UiMediaScope.

Parâmetro Tipo de valor Descrição
windowWidth Dp A largura atual da janela em dp.
windowHeight Dp A altura atual da janela em dp.
windowPosture UiMediaScope.Posture A posição atual da janela do aplicativo.
pointerPrecision UiMediaScope.PointerPrecision A maior precisão dos dispositivos apontadores disponíveis.
keyboardKind UiMediaScope.KeyboardKind O tipo de teclado disponível ou conectado.
hasCamera Boolean Se a câmera é compatível com o dispositivo.
hasMicrophone Boolean Se o microfone é compatível com o dispositivo.
viewingDistance UiMediaScope.ViewingDistance A distância típica entre o usuário e a tela do dispositivo.

Um objeto UiMediaScope resolve os valores dos parâmetros. A função mediaQuery usa LocalUiMediaScope.current para acessar o objeto UiMediaScope, que representa as capacidades e o contexto do dispositivo atual. Esse objeto é atualizado dinamicamente quando são feitas mudanças, como quando o usuário muda a posição do dispositivo. A função mediaQuery avalia a lambda query com o objeto UiMediaScope atualizado e retorna um valor booleano. Por exemplo, o trecho a seguir escolhe entre TabletopLayout e FlatLayout com base no valor do parâmetro windowPosture.

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

Tomar uma decisão com base no tamanho da janela

Classes de tamanho de janela são um conjunto de pontos de interrupção específicos de janela de visualização que ajudam você a projetar, desenvolver e testar layouts adaptáveis. É possível comparar os dois parâmetros que representam o tamanho atual da janela com o limite definido nas classes de tamanho de janela. O exemplo a seguir muda o número de painéis de acordo com a largura da janela. WindowSizeClass classe tem constantes para os limites das classes de tamanho de janela (Figura 1).

A função derivedMediaQuery avalia a lambda query e envolve o resultado em um derivedStateOf. Como windowWidth e windowHeight podem ser atualizados com frequência, chame a função derivedMediaQuery em vez da função mediaQuery quando você se referir a esses parâmetros na 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. O layout é atualizado de acordo com a largura da janela.

Atualizar o layout de acordo com a posição da janela

O parâmetro windowPosture descreve a posição atual da janela como um objeto UiMediaScope.Posture. É possível verificar a postura atual comparando o parâmetro com os valores definidos na UiMediaScope.Posture classe. O exemplo a seguir muda o layout de acordo com a posição da janela:

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

Verificar a precisão do dispositivo apontador disponível

Um dispositivo apontador de alta precisão ajuda os usuários a apontar um elemento da interface com precisão. A precisão de um dispositivo apontador depende do tipo de dispositivo.

O parâmetro pointerPrecision descreve a precisão dos dispositivos apontadores disponíveis, como um mouse e uma tela sensível ao toque. Há quatro valores definidos na classe UiMediaScope.PointerPrecision: Fine, Coarse, Blunt e None. None significa que nenhum dispositivo apontador está disponível. A precisão varia de maior para menor nesta ordem: Fine, Coarse e Blunt.

Se vários dispositivos apontadores estiverem disponíveis e as precisões forem diferentes, o parâmetro será resolvido com o mais alto. Por exemplo, se houver dois dispositivos apontadores, um dispositivo de precisão Fine e um dispositivo de precisão Blunt, Fine será o valor do parâmetro pointerPrecision.

O exemplo a seguir mostra um botão maior quando o usuário está usando um dispositivo apontador com baixa precisão:

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

Verificar o tipo de teclado disponível

O parâmetro keyboardKind representa o tipo de teclados disponíveis: Physical, Virtual e None. Se um teclado na tela for exibido e um teclado de hardware estiver disponível ao mesmo tempo, o parâmetro será resolvido como Physical. Se nenhum for detectado, None será o valor do parâmetro. O exemplo a seguir mostra uma mensagem sugerindo que os usuários conectem um teclado quando nenhum teclado for detectado:

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

Verificar se o dispositivo oferece suporte à câmera e ao microfone

Alguns dispositivos não oferecem suporte a câmeras ou microfones. É possível verificar se o dispositivo oferece suporte a uma câmera e um microfone com o parâmetro hasCamera e o parâmetro hasMicrophone. O exemplo a seguir mostra botões para usar com a câmera e o microfone quando o dispositivo oferece suporte a eles:

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

Ajustar a interface com a distância de visualização estimada

A distância de visualização é um fator que ajuda a determinar o layout. Se o usuário estiver usando o app à distância, ele vai esperar que o texto e os elementos da interface sejam maiores. O parâmetro viewingDistance fornece uma estimativa da distância de visualização com base no tipo de dispositivo e no contexto de uso típico.

Há três valores definidos na classe UiMediaScope.ViewingDistance: Near, Medium e Far. Near significa que a tela está em um alcance próximo, e Far significa que o dispositivo é visualizado à distância. O exemplo a seguir aumenta o tamanho da fonte quando a distância de visualização é Far ou Medium:

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

Visualizar um componente da interface

É possível chamar as funções mediaQuery e derivedMediaQuery nas funções combináveis para visualizar componentes da interface. O snippet a seguir escolhe entre TabletopLayout e FlatLayout com base no valor de parâmetro windowPosture. Para visualizar o TabletopLayout, o parâmetro windowPosture precisa ser UiMediaScope.Posture.Tabletop.

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

As funções mediaQuery e derivedMediaQuery avaliam a lambda query fornecida em um objeto UiMediaScope, que é fornecido como LocalUiMediaScope.current. É possível substituir isso seguindo estas etapas:

  1. Ativar a função mediaQuery.
  2. Defina um objeto personalizado que implemente a interface UiMediaScope.
  3. Defina o objeto personalizado como o LocalUiMediaScope com a função CompositionLocalProvider.
  4. Chame o combinável para visualizar na lambda de conteúdo da função CompositionLocalProvider.

É possível visualizar o TabletopLayout com o exemplo a seguir:

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