Sobre o WindowInsetsRulers

WindowInsets é a API padrão no Jetpack Compose para processar áreas da tela que estão parcialmente ou totalmente obscurecidas pela interface do sistema. Essas áreas incluem a barra de status, a barra de navegação e o teclado na tela. Como alternativa, você pode transmitir WindowInsetsRulers predefinidos, como SafeDrawing, para Modifier.fitInside ou Modifier.fitOutside para alinhar o conteúdo com as barras de sistema e o corte da tela ou criar WindowInsetsRulers personalizados.

Vantagens de WindowInsetsRulers

  • Evita a complexidade do consumo: opera durante a fase de posicionamento do layout. Isso significa que ele ignora completamente a cadeia de consumo de encartes e sempre pode fornecer as posições absolutas corretas das barras de sistema e dos cortes da tela, independentemente do que os layouts pai fizeram. O uso dos métodos Modifier.fitInside ou Modifier.fitOutside é útil para corrigir problemas quando os elementos combináveis ancestrais consomem encartes incorretamente.
  • Evita facilmente as barras de sistema: ajuda o conteúdo do app a evitar as barras de sistema e o corte da tela, e pode ser mais simples do que usar WindowInsets diretamente.
  • Altamente personalizável: os desenvolvedores podem alinhar o conteúdo a réguas personalizadas e ter controle preciso sobre os layouts com layouts personalizados.

Desvantagens de WindowInsetsRulers

  • Não pode ser usado para medição: como opera durante a fase de posicionamento, as informações posicionais fornecidas não estão disponíveis durante a fase de medição anterior.

Alinhar o conteúdo com métodos de modificador

Modifier.fitInside permite que os apps alinhem o conteúdo às barras de sistema e aos cortes da tela. Ele pode ser usado no lugar de WindowInsets. Modifier.fitOutside geralmente é o inverso de Modifier.fitInside.

Por exemplo, para verificar se o conteúdo do app evita as barras de sistema e o corte da tela, use fitInside(WindowInsetsRulers.safeDrawing.current).

@Composable
fun FitInsideDemo(modifier: Modifier) {
    Box(
        modifier = modifier
            .fillMaxSize()
            // Or DisplayCutout, Ime, NavigationBars, StatusBar, etc...
            .fitInside(WindowInsetsRulers.SafeDrawing.current)
    )
}

A tabela a seguir mostra como o conteúdo do app ficaria com réguas predefinidas com Modifier.fitInside ou Modifier.fitOutside.

Tipo de régua predefinida

Modifier.fitInside

Modifier.fitOutside

DisplayCutout

Ime

N/A

NavigationBars

SafeDrawing

N/A (use StatusBar, CaptionBar, NavigationBar)

StatusBar

O uso de Modifier.fitInside e Modifier.fitOutside exige que os elementos combináveis sejam restritos. Isso significa que você precisa definir modificadores como Modifier.size ou Modifier.fillMaxSize.

Algumas réguas, como Modifier.fitOutside em SafeDrawing e SystemBars, retornam várias réguas. Nesse caso, o Android coloca o elemento combinável usando uma régua da esquerda, de cima, da direita e de baixo.

Evitar o IME com Modifier.fitInside

Para processar elementos inferiores com um IME com Modifier.fitInside, transmita um RectRuler que usa o valor mais interno de NavigationBar e Ime.

@Composable
fun FitInsideWithImeDemo(modifier: Modifier) {
    Box(
        modifier = modifier
            .fillMaxSize()
            .fitInside(
                RectRulers.innermostOf(
                    WindowInsetsRulers.NavigationBars.current,
                    WindowInsetsRulers.Ime.current
                )
            )
    ) {
        TextField(
            value = "Demo IME Insets",
            onValueChange = {},
            modifier = modifier.align(Alignment.BottomStart).fillMaxWidth()
        )
    }
}

Evitar a barra de status e a barra de legenda com Modifier.fitInside

Da mesma forma, para verificar se os elementos superiores evitam a barra de status e a barra de legenda com Modifier.fitInsider, transmita um RectRuler que usa o valor mais interno de StatusBars e CaptionBar.

@Composable
fun FitInsideWithStatusAndCaptionBarDemo(modifier: Modifier) {
    Box(
        modifier = modifier
            .fillMaxSize()
            .fitInside(
                RectRulers.innermostOf(
                    WindowInsetsRulers.StatusBars.current,
                    WindowInsetsRulers.CaptionBar.current
                )
            )
    )
}

Criar WindowInsetsRulers personalizados

Você pode alinhar o conteúdo a réguas personalizadas. Por exemplo, considere o caso de uso em que um elemento combinável pai processa encartes de maneira inadequada, causando problemas de padding em um filho downstream. Embora esse problema possa ser resolvido de outras maneiras, incluindo o uso de Modifier.fitInside, você também pode criar uma régua personalizada para alinhar com precisão o elemento combinável filho sem precisar corrigir o problema no pai upstream, conforme mostrado no exemplo e no vídeo a seguir:

@Composable
fun WindowInsetsRulersDemo(modifier: Modifier) {
    Box(
        contentAlignment = BottomCenter,
        modifier = modifier
            .fillMaxSize()
            // The mistake that causes issues downstream, as .padding doesn't consume insets.
            // While it's correct to instead use .windowInsetsPadding(WindowInsets.navigationBars),
            // assume it's difficult to identify this issue to see how WindowInsetsRulers can help.
            .padding(WindowInsets.navigationBars.asPaddingValues())
    ) {
        TextField(
            value = "Demo IME Insets",
            onValueChange = {},
            modifier = modifier
                // Use alignToSafeDrawing() instead of .imePadding() to precisely place this child
                // Composable without having to fix the parent upstream.
                .alignToSafeDrawing()

            // .imePadding()
            // .fillMaxWidth()
        )
    }
}

fun Modifier.alignToSafeDrawing(): Modifier {
    return layout { measurable, constraints ->
        if (constraints.hasBoundedWidth && constraints.hasBoundedHeight) {
            val placeable = measurable.measure(constraints)
            val width = placeable.width
            val height = placeable.height
            layout(width, height) {
                val bottom = WindowInsetsRulers.SafeDrawing.current.bottom
                    .current(0f).roundToInt() - height
                val right = WindowInsetsRulers.SafeDrawing.current.right
                    .current(0f).roundToInt()
                val left = WindowInsetsRulers.SafeDrawing.current.left
                    .current(0f).roundToInt()
                measurable.measure(Constraints.fixed(right - left, height))
                    .place(left, bottom)
            }
        } else {
            val placeable = measurable.measure(constraints)
            layout(placeable.width, placeable.height) {
                placeable.place(0, 0)
            }
        }
    }
}

O vídeo a seguir mostra um exemplo de consumo problemático de encarte do IME causado por um pai upstream na imagem à esquerda e o uso de réguas personalizadas para corrigir o problema à direita. O padding extra é mostrado abaixo do elemento combinável TextField, porque o padding da barra de navegação não foi consumido pelo pai. O filho é colocado no local correto na imagem à direita usando uma régua personalizada, conforme mostrado no exemplo de código anterior.

Verificar se os pais estão restritos

Para usar WindowInsetsRulers com segurança, verifique se o pai fornece restrições válidas. Os pais precisam ter um tamanho definido e não podem depender do tamanho de um filho que usa WindowInsetsRulers. Use fillMaxSize ou outros modificadores de tamanho em elementos combináveis pai.

Da mesma forma, colocar um elemento combinável que usa WindowInsetsRulers dentro de um contêiner de rolagem, como verticalScroll, pode causar um comportamento inesperado, já que o contêiner de rolagem fornece restrições de altura não vinculadas, que são incompatíveis com a lógica da régua.