Encartes de janela no Compose

A plataforma Android é responsável por mostrar a interface do sistema, como a barra de status e de navegação. Essa interface do sistema é mostrada independente do app que o usuário esteja usando. O WindowInsets fornece informações sobre a interface do sistema para garantir que o app seja desenhado na área correta e que a interface não seja sobreposta pela interface do sistema.

Indo de ponta a ponta para desenhar atrás das barras do sistema
Figura 1. Indo de ponta a ponta para desenhar atrás das barras do sistema.

Por padrão, a interface do app se restringe a ser disposta na interface do sistema, como a barra de status e a barra de navegação. Isso garante que o conteúdo do app não seja sobreposto por elementos da interface do sistema.

No entanto, recomendamos que os apps ativem a exibição nessas áreas em que a interface do sistema também está sendo exibida, o que resulta em uma experiência do usuário mais integrada e permite que o app aproveite ao máximo o espaço da janela disponível. Isso também permite que os apps sejam animados junto com a interface do sistema, especialmente ao mostrar e ocultar o teclado de software.

A ativação da exibição nessas regiões e da exibição de conteúdo por trás da interface do sistema é chamada de ir de ponta a ponta. Nesta página, você vai aprender sobre os diferentes tipos de encartes, como ativar a exibição de ponta a ponta e como usar as APIs de encarte para animar a interface e evitar ocultar partes do app.

Conceitos básicos do encarte

Quando um app passa de ponta a ponta, você precisa garantir que conteúdo e interações importantes não sejam ocultados pela interface do sistema. Por exemplo, se um botão for colocado atrás da barra de navegação, o usuário poderá não conseguir clicar nele.

O tamanho da interface do sistema e as informações sobre onde ele é colocado são especificados por encartes.

Cada parte da interface do sistema tem um tipo correspondente de encarte que descreve o tamanho e onde é colocado. Por exemplo, os encartes da barra de status fornecem o tamanho e a posição dela, enquanto os encartes da barra de navegação fornecem o tamanho e a posição dela. Cada tipo de encarte consiste em quatro dimensões de pixels: superior, esquerdo, direito e inferior. Essas dimensões especificam até que ponto a interface do sistema se estende dos lados correspondentes da janela do app. Para evitar a sobreposição com esse tipo de interface do sistema, a interface do app precisa ser inserida por esse valor.

Estes tipos de encarte integrados do Android estão disponíveis pelo WindowInsets:

WindowInsets.statusBars

Os encartes que descrevem as barras de status. Essas são as principais barras da interface do sistema que contêm ícones de notificação e outros indicadores.

WindowInsets.statusBarsIgnoringVisibility

Os encartes da barra de status para quando estão visíveis. Se as barras de status estiverem ocultas (devido ao entrar no modo de tela cheia imersiva), os encartes da barra de status principal estarão vazios, mas esses encartes não estarão vazios.

WindowInsets.navigationBars

Os encartes que descrevem as barras de navegação. Essas são as barras de interface do sistema à esquerda, à direita ou na parte de baixo do dispositivo, que descrevem a barra de tarefas ou os ícones de navegação. Eles podem mudar durante a execução com base no método de navegação preferido do usuário e na interação com a barra de tarefas.

WindowInsets.navigationBarsIgnoringVisibility

As inserções da barra de navegação para quando ficam visíveis. Se as barras de navegação estiverem ocultas (devido ao modo de tela cheia imersiva), os encartes da barra de navegação principal estarão vazios, mas não estarão vazios.

WindowInsets.captionBar

O encarte que descreve a decoração da janela da interface do sistema, se estiver em uma janela de formato livre, como a barra de título superior.

WindowInsets.captionBarIgnoringVisibility

A barra de legenda é inserida para quando ficam visíveis. Se as barras de legenda estiverem ocultas, os encartes da barra de legenda principal estarão vazios, mas não estarão.

WindowInsets.systemBars

A união dos encartes da barra do sistema, que incluem as barras de status, de navegação e de legenda.

WindowInsets.systemBarsIgnoringVisibility

As inserções da barra de sistema para quando ficam visíveis. Se as barras do sistema estiverem ocultas no momento (devido à entrada no modo imersivo de tela cheia), os encartes da barra do sistema principal estarão vazios, mas não estarão vazios.

WindowInsets.ime

Os encartes que descrevem a quantidade de espaço na parte inferior que o teclado de software ocupa.

WindowInsets.imeAnimationSource

Os encartes que descrevem a quantidade de espaço que o teclado de software ocupava antes da animação atual do teclado.

WindowInsets.imeAnimationTarget

Os encartes que descrevem a quantidade de espaço que o teclado de software ocupará após a animação atual do teclado.

WindowInsets.tappableElement

Um tipo de encartes que descreve informações mais detalhadas sobre a interface de navegação, fornecendo a quantidade de espaço em que os "toques" serão processados pelo sistema, e não pelo app. Para barras de navegação transparentes com navegação por gestos, alguns elementos do app podem ser tocados na interface de navegação do sistema.

WindowInsets.tappableElementIgnoringVisibility

Os encartes de elemento tocável são inseridos para quando estão visíveis. Se os elementos tocáveis estiverem ocultos (devido ao modo de tela cheia imersivo), os encartes de elementos tocáveis principais vão estar vazios, mas não vão ficar.

WindowInsets.systemGestures

São os encartes que representam a quantidade de encartes em que o sistema vai interceptar gestos para navegação. Os apps podem especificar manualmente o processamento de uma quantidade limitada desses gestos usando o Modifier.systemGestureExclusion.

WindowInsets.mandatorySystemGestures

Um subconjunto dos gestos do sistema que sempre serão gerenciados por ele e que não podem ser desativados pelo Modifier.systemGestureExclusion.

WindowInsets.displayCutout

Os encartes que representam a quantidade de espaçamento necessário para evitar a sobreposição com um recorte da tela (entalhe ou pinhole).

WindowInsets.waterfall

Os encartes que representam as áreas curvas de uma exibição de cascata. Uma exibição em cascata tem áreas curvas ao longo das bordas da tela, onde a tela começa a se envolver ao longo das laterais do dispositivo.

Esses tipos são resumidos em três tipos de encarte "seguros" que garantem que o conteúdo não seja ocultado:

Esses tipos de encarte "seguros" protegem o conteúdo de maneiras diferentes, com base nos encartes da plataforma subjacentes:

  • Use WindowInsets.safeDrawing para proteger conteúdo que não pode ser desenhado em nenhuma interface do sistema. Esse é o uso mais comum de encartes: para evitar mostrar conteúdo obscurecido pela interface do sistema (parcial ou completamente).
  • Use WindowInsets.safeGestures para proteger o conteúdo com gestos. Isso evita que os gestos do sistema entrem em conflito com os do app (como os usados nas páginas inferiores, carrosséis ou jogos).
  • Use WindowInsets.safeContent como uma combinação de WindowInsets.safeDrawing e WindowInsets.safeGestures para garantir que o conteúdo não tenha sobreposição visual nem sobreposição de gestos.

Configuração de encartes

Para permitir que o app tenha controle total sobre onde o conteúdo será mostrado, siga estas etapas de configuração. Sem essas etapas, o app pode mostrar cores pretas ou sólidas por trás da interface do sistema ou não ser animado de forma síncrona com o teclado de software.

  1. Chame enableEdgeToEdge() em Activity.onCreate. Essa chamada solicita que o app seja mostrado por trás da interface do sistema. Seu app controlará como esses encartes são usados para ajustar a interface.
  2. Defina android:windowSoftInputMode="adjustResize" na entrada AndroidManifest.xml da atividade. Essa configuração permite que o app receba o tamanho do IME do software como encartes, que você pode usar para preencher e posicionar o conteúdo adequadamente quando o IME aparece e desaparece no app.

    <!-- in your AndroidManifest.xml file: -->
    <activity
      android:name=".ui.MainActivity"
      android:label="@string/app_name"
      android:windowSoftInputMode="adjustResize"
      android:theme="@style/Theme.MyApplication"
      android:exported="true">
    

APIs do Compose

Depois que a atividade assumir o controle do processamento de todos os encartes, você poderá usar as APIs do Compose para garantir que o conteúdo não seja obscurecido e que os elementos interativos não se sobreponham à interface do sistema. Essas APIs também sincronizam o layout do app com mudanças inseridas.

Por exemplo, este é o método mais básico de aplicar os encartes ao conteúdo de todo o app:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    enableEdgeToEdge()

    setContent {
        Box(Modifier.safeDrawingPadding()) {
            // the rest of the app
        }
    }
}

Esse snippet aplica os encartes da janela safeDrawing como padding ao redor de todo o conteúdo do app. Isso garante que os elementos interativos não se sobreponham à interface do sistema, mas também significa que nenhum app será desenhado por trás da interface do sistema para ter um efeito de borda a borda. Para usar ao máximo toda a janela, você precisa ajustar onde os encartes serão aplicados, tela ou componente a componente.

Todos esses tipos de encarte são animados automaticamente com animações do IME backportadas para a API 21. Por extensão, todos os layouts que usam esses encartes também são animados automaticamente à medida que os valores do encarte mudam.

Há duas maneiras principais de usar esses tipos de encarte para ajustar os layouts combináveis: modificadores de padding e de tamanho de encarte.

Modificadores de padding

Modifier.windowInsetsPadding(windowInsets: WindowInsets) aplica os encartes de janela fornecidos como padding, atuando da mesma forma que Modifier.padding. Por exemplo, Modifier.windowInsetsPadding(WindowInsets.safeDrawing) aplica os encartes de desenho seguros como padding nos quatro lados.

Há também vários métodos utilitários integrados para os tipos de encarte mais comuns. Modifier.safeDrawingPadding() é um desses métodos, equivalente a Modifier.windowInsetsPadding(WindowInsets.safeDrawing). Há modificadores análogos para os outros tipos de encarte.

Modificadores de tamanho do encarte

Os modificadores abaixo aplicam uma quantidade de encartes de janela, definindo o tamanho do componente como o tamanho dos encartes:

Modifier.windowInsetsStartWidth(windowInsets: WindowInsets)

Aplica o lado inicial de windowInsets como largura (como Modifier.width).

Modifier.windowInsetsEndWidth(windowInsets: WindowInsets)

Aplica o lado final de windowInsets como largura (por exemplo, Modifier.width).

Modifier.windowInsetsTopHeight(windowInsets: WindowInsets)

Aplica o lado de cima de windowInsets como altura (como Modifier.height).

Modifier.windowInsetsBottomHeight(windowInsets: WindowInsets)

Aplica a parte de baixo de windowInsets como altura (por exemplo, Modifier.height).

Esses modificadores são especialmente úteis para dimensionar uma Spacer que ocupa o espaço de encartes:

LazyColumn(
    Modifier.imePadding()
) {
    // Other content
    item {
        Spacer(
            Modifier.windowInsetsBottomHeight(
                WindowInsets.systemBars
            )
        )
    }
}

Consumo do encarte

Os modificadores de padding do encarte (windowInsetsPadding e auxiliares, como safeDrawingPadding) consomem automaticamente a parte dos encartes aplicados como padding. Ao se aprofundar na árvore de composição, os modificadores de padding aninhados e os de tamanho de encarte sabem que parte dos encartes já foram consumidas pelos modificadores de padding externos e evitem usar a mesma parte dos encartes mais de uma vez, o que resultaria em muito espaço extra.

Os modificadores de tamanho de encarte também evitam usar a mesma parte de encartes mais de uma vez se os encartes já tiverem sido consumidos. No entanto, como eles mudam o tamanho diretamente, não consomem encartes.

Como resultado, os modificadores de padding do aninhamento mudam automaticamente a quantidade de padding aplicada a cada elemento combinável.

No mesmo exemplo de LazyColumn de antes, a LazyColumn está sendo redimensionada pelo modificador imePadding. Dentro do LazyColumn, o último item é dimensionado de acordo com a altura da parte de baixo das barras do sistema:

LazyColumn(
    Modifier.imePadding()
) {
    // Other content
    item {
        Spacer(
            Modifier.windowInsetsBottomHeight(
                WindowInsets.systemBars
            )
        )
    }
}

Quando o IME é fechado, o modificador imePadding() não aplica padding, já que o IME não tem altura. Como o modificador imePadding() não está aplicando padding, nenhum encarte está sendo consumido, e a altura da Spacer será o tamanho da parte de baixo das barras do sistema.

Quando o IME é aberto, as inserções do IME são animadas para corresponder ao tamanho do IME, e o modificador imePadding() começa a aplicar o padding inferior para redimensionar o LazyColumn à medida que o IME é aberto. À medida que o modificador imePadding() começa a aplicar o padding da parte de baixo, ele também começa a consumir essa quantidade de encartes. Portanto, a altura do Spacer começa a diminuir, já que parte do espaçamento das barras do sistema já foi aplicado pelo modificador imePadding(). Depois que o modificador imePadding() estiver aplicando um volume maior de padding da parte de baixo do que as barras do sistema, a altura da Spacer será zero.

Quando o IME é fechado, as mudanças acontecem ao contrário: o Spacer começa a expandir de uma altura de zero quando o imePadding() está aplicando menos do que o lado inferior das barras do sistema, até que o Spacer corresponda à altura do lado inferior das barras depois que o IME for completamente animado.

Figura 2. Coluna lenta de ponta a ponta com TextField

Esse comportamento é realizado pela comunicação entre todos os modificadores windowInsetsPadding e pode ser influenciado de algumas outras maneiras.

O Modifier.consumeWindowInsets(insets: WindowInsets) também consome os encartes da mesma forma que Modifier.windowInsetsPadding, mas não aplica os encartes consumidos como padding. Isso é útil em combinação com os modificadores de tamanho de encarte para indicar aos irmãos que uma certa quantidade de encartes já foi consumida:

Column(Modifier.verticalScroll(rememberScrollState())) {
    Spacer(Modifier.windowInsetsTopHeight(WindowInsets.systemBars))

    Column(
        Modifier.consumeWindowInsets(
            WindowInsets.systemBars.only(WindowInsetsSides.Vertical)
        )
    ) {
        // content
        Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime))
    }

    Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.systemBars))
}

Modifier.consumeWindowInsets(paddingValues: PaddingValues) se comporta de maneira muito semelhante à versão com um argumento WindowInsets, mas usa um PaddingValues arbitrário para consumir. Isso é útil para informar aos filhos quando o padding ou o espaçamento é fornecido por algum outro mecanismo além dos modificadores de padding do encarte, como um Modifier.padding comum ou espaçadores de altura fixos:

@OptIn(ExperimentalLayoutApi::class)
Column(Modifier.padding(16.dp).consumeWindowInsets(PaddingValues(16.dp))) {
    // content
    Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime))
}

Nos casos em que os encartes de janela brutos são necessários sem consumo, use os valores de WindowInsets diretamente ou use WindowInsets.asPaddingValues() para retornar um PaddingValues dos encartes que não são afetados pelo consumo. No entanto, devido às ressalvas abaixo, sempre que possível, prefira usar modificadores de padding e modificadores de tamanho de encartes de janela.

Encartes e fases do Jetpack Compose

O Compose usa as principais APIs do AndroidX para atualizar e animar encartes, que usam as APIs de plataforma para gerenciar encartes. Devido a esse comportamento da plataforma, os encartes têm uma relação especial com as fases do Jetpack Compose.

O valor dos encartes é atualizado após a fase de composição, mas antes da fase de layout. Isso significa que a leitura do valor dos encartes na composição geralmente usa um valor dos encartes que está um frame atrasado. Os modificadores integrados descritos nesta página foram criados para atrasar o uso dos valores dos encartes até a fase de layout, o que garante que os valores inseridos sejam usados no mesmo frame em que são atualizados.

Animações do IME (editor de método de entrada, na sigla em inglês) do teclado com WindowInsets

Você pode aplicar Modifier.imeNestedScroll() a um contêiner de rolagem para abrir e fechar o IME automaticamente ao rolar até a parte de baixo do contêiner.

class WindowInsetsExampleActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        WindowCompat.setDecorFitsSystemWindows(window, false)

        setContent {
            MaterialTheme {
                MyScreen()
            }
        }
    }
}

@OptIn(ExperimentalLayoutApi::class)
@Composable
fun MyScreen() {
    Box {
        LazyColumn(
            modifier = Modifier
                .fillMaxSize() // fill the entire window
                .imePadding() // padding for the bottom for the IME
                .imeNestedScroll(), // scroll IME at the bottom
            content = { }
        )
        FloatingActionButton(
            modifier = Modifier
                .align(Alignment.BottomEnd)
                .padding(16.dp) // normal 16dp of padding for FABs
                .navigationBarsPadding() // padding for navigation bar
                .imePadding(), // padding for when IME appears
            onClick = { }
        ) {
            Icon(imageVector = Icons.Filled.Add, contentDescription = "Add")
        }
    }
}

Animação mostrando um elemento da IU rolando para cima e para baixo para dar lugar ao teclado.

Figura 1. Animações do IME (editor de método de entrada, na sigla em inglês).

Suporte de encarte para componentes do Material 3

Para facilitar o uso, muitos dos elementos combináveis integrados do Material 3 (androidx.compose.material3) processam os próprios encartes, com base em como os elementos são colocados no app de acordo com as especificações do Material Design.

Processamento de encarte de elementos combináveis

Confira abaixo uma lista dos componentes do Material Design que processam automaticamente encartes.

Barras de apps

Contêineres de conteúdo

Scaffold

Por padrão, Scaffold fornece encartes como parâmetro paddingValues para você consumir e usar. Scaffold não aplica os encartes ao conteúdo. Essa responsabilidade é sua. Por exemplo, para consumir esses encartes com um LazyColumn dentro de um Scaffold:

Scaffold { innerPadding ->
    // innerPadding contains inset information for you to use and apply
    LazyColumn(
        // consume insets as scaffold doesn't do it by default
        modifier = Modifier.consumeWindowInsets(innerPadding),
        contentPadding = innerPadding
    ) {
        items(count = 100) {
            Box(
                Modifier
                    .fillMaxWidth()
                    .height(50.dp)
                    .background(colors[it % colors.size])
            )
        }
    }
}

Substituir encartes padrão

Você pode mudar o parâmetro windowInsets transmitido ao elemento combinável para configurar o comportamento dele. Esse parâmetro pode ser um tipo diferente de encarte de janela a ser aplicado ou desativado com uma instância vazia: WindowInsets(0, 0, 0, 0).

Por exemplo, para desativar o processamento do encarte em LargeTopAppBar, defina o parâmetro windowInsets como uma instância vazia:

LargeTopAppBar(
    windowInsets = WindowInsets(0, 0, 0, 0),
    title = {
        Text("Hi")
    }
)

Interoperabilidade com encartes do sistema de visualização

Pode ser necessário substituir os encartes padrão quando a tela tiver código das visualizações e do Compose na mesma hierarquia. Nesse caso, é preciso deixar claro qual vai consumir os encartes e qual deve ser ignorado.

Por exemplo, se o layout mais externo for um layout de visualização do Android, consome os encartes do sistema de visualização e os ignore no Compose. Como alternativa, caso o layout mais externo seja um elemento combinável, consuma os encartes no Compose e preencha os elementos combináveis AndroidView corretamente.

Por padrão, cada ComposeView consome todos os encartes no nível de consumo WindowInsetsCompat. Para mudar esse comportamento padrão, defina ComposeView.consumeWindowInsets como false.

Recursos

  • Now in Android (link em inglês): um app Android totalmente funcional criado com o Kotlin e o Jetpack Compose.