Mostre conteúdo de ponta a ponta no app e processe encartes de janela no Compose

A plataforma Android é responsável por desenhar a interface do sistema, como a barra de status e a barra de navegação. Essa interface do sistema é exibida independentemente do app que o usuário está usando.

WindowInsets fornece informações sobre a interface do sistema para garantir que o app seja renderizado na área correta e que a interface não seja obscurecida pela interface do sistema.

Como usar ponta a ponta para desenhar por trás das barras do sistema
Figura 1. Vai de borda a borda para desenhar atrás das barras do sistema.

No Android 14 (nível 34 da API) e versões anteriores, a interface do app não é mostrada abaixo da tela as barras de sistema e cortes da tela por padrão.

No Android 15 (nível 35 da API) e versões mais recentes, o app é renderizado abaixo das barras do sistema e exibe recortes quando o app é direcionado ao SDK 35. Isso resulta em um uma experiência do usuário perfeita e permite que seu aplicativo aproveite ao máximo a espaço disponível na janela.

A exibição de conteúdo por trás da IU do sistema é chamada de de ponta a ponta. Neste você aprende sobre os diferentes tipos de encarte, como passar de ponta a ponta, e como usar as APIs de encarte para animar a interface e garantir que o conteúdo do app não seja ocultado por elementos da interface do sistema.

Conceitos básicos de inserção

Quando um app é exibido de ponta a ponta, é necessário garantir que o conteúdo e as interações importantes não sejam obscurecidos pela interface do sistema. Por exemplo, se um botão for colocado atrás da barra de navegação, talvez o usuário não consiga clicar nele.

O tamanho da interface do sistema e as informações sobre onde ela é colocada 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 da barra de status, enquanto os encartes da barra de navegação fornecem tamanho e posição da barra de navegação. Cada tipo de inseto consiste em quatro dimensões de pixel: superior, esquerda, direita e inferior. Essas dimensões especificam até onde 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 nesse valor.

Estes tipos de inset integrados do Android estão disponíveis no WindowInsets:

WindowInsets.statusBars

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

WindowInsets.statusBarsIgnoringVisibility

Os engastes da barra de status quando eles estão visíveis. Se as barras de status estiverem ocultas (por causa da entrada no modo de tela cheia imersiva), os insetos da barra de status principal vão estar vazios, mas não vão estar vazios.

WindowInsets.navigationBars

Encartes que descrevem as barras de navegação. São as barras da interface do sistema no lado esquerdo, direito ou inferior 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

Os encartes da barra de navegação para quando ficam visíveis. Se as barras de navegação estiverem ocultas no momento (devido ao entrar no modo de tela cheia imersivo), os encartes da barra de navegação principal vão estar vazios, mas esses encartes não vão estar vazios.

WindowInsets.captionBar

O encarte descrevendo 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

Os insetos da barra de legendas quando estão visíveis. Se as barras de legenda estiverem ocultas, os insetos da barra de legenda principal vão estar vazios, mas não vão estar vazios.

WindowInsets.systemBars

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

WindowInsets.systemBarsIgnoringVisibility

Os encartes da barra do sistema para quando ficam visíveis. Se as barras de sistema estiverem escondidas no momento (devido ao entrar no modo de tela cheia imersivo), os encartes da barra de sistema principal vão estar vazios, mas esses encartes não vão estar.

WindowInsets.ime

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

WindowInsets.imeAnimationSource

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

WindowInsets.imeAnimationTarget

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

WindowInsets.tappableElement

Um tipo de encarte que descreve informações mais detalhadas sobre a interface de navegação, informando a quantidade de espaço em que o "toca" serão processados pelo sistema, não pelo aplicativo. Para barras de navegação transparentes com navegação por gestos, é possível tocar em alguns elementos do app na interface de navegação do sistema.

WindowInsets.tappableElementIgnoringVisibility

Os encartes de elemento tocável para quando ficam visíveis. Se os elementos que podem ser tocados estiverem ocultos (por causa da entrada no modo de tela cheia imersiva), os insetos principais do elemento que pode ser tocado vão estar vazios, mas não vão estar vazios.

WindowInsets.systemGestures

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 processados pelo sistema e não podem ser desativados por Modifier.systemGestureExclusion.

WindowInsets.displayCutout

Os insetos representam a quantidade de espaço necessária para evitar a sobreposição com um recorte de tela (entalhe ou orifício).

WindowInsets.waterfall

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

Esses tipos são resumidos por três tipos de inserção "seguros" que garantem que o conteúdo não seja obscurecido:

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

  • Use WindowInsets.safeDrawing para proteger conteúdo que não pode ser desenhado em qualquer IU do sistema. Este é o uso mais comum de encartes: para evitar mostrar conteúdo oculto pela interface do sistema (parcialmente ou completamente).
  • Use WindowInsets.safeGestures para proteger o conteúdo com gestos. Isso evita que os gestos do sistema entrem em conflito com os gestos do app (como os de 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 de gestos.

Configuração de encartes

Para permitir que o app tenha controle total sobre onde ele desenha conteúdo, siga estas configurações etapas. Sem essas etapas, o app pode desenhar cores pretas ou sólidas por trás do interface do sistema ou não anime de forma síncrona com o teclado de software.

  1. Segmente o SDK 35 ou mais recente para forçar a tela cheia no Android 15 e versões mais recentes. Seu app é mostrado por trás da IU do sistema. É possível ajustar a interface do app processando insets.
  2. Opcionalmente, chame enableEdgeToEdge() em Activity.onCreate(), o que permite que o app seja exibido de ponta a ponta em versões anteriores do Android.
  3. Defina android:windowSoftInputMode="adjustResize" nas suas atividades Entrada AndroidManifest.xml. Essa configuração permite que o app receba o tamanho do IME do software como insets, que podem ser usados 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 Compose

Depois que a atividade assumir o controle do processamento de todos os insets, 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 alterações de encarte.

Por exemplo, esse é 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 da todo o conteúdo do app. Isso garante que os elementos interativos não se sobreponham à IU do sistema, isso também significa que nenhum aplicativo será desenhado a IU do sistema para obter um efeito de ponta a ponta. Para aproveitar ao máximo você precisa ajustar onde os encartes são aplicados em uma tela, tela por tela. ou componente por componente.

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

Há duas maneiras principais de usar esses tipos de inseto para ajustar os layouts do elemento combinável: modificadores de padding e modificadores de tamanho de inseto.

Modificadores de padding

Modifier.windowInsetsPadding(windowInsets: WindowInsets) aplica os insets de janela fornecidos como padding, agindo exatamente como Modifier.padding. Por exemplo, Modifier.windowInsetsPadding(WindowInsets.safeDrawing) se aplica o encarte seguro do desenho 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 inset.

Modificadores de tamanho de encarte

Os modificadores a seguir aplicam uma quantidade de engastes de janela definindo o tamanho do componente como o tamanho dos engastes:

Modifier.windowInsetsStartWidth(windowInsets: WindowInsets)

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

Modifier.windowInsetsEndWidth(windowInsets: WindowInsets)

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

Modifier.windowInsetsTopHeight(windowInsets: WindowInsets)

Aplica a parte de cima de windowInsets como a altura (como Modifier.height).

Modifier.windowInsetsBottomHeight(windowInsets: WindowInsets)

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

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

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

Consumo de Inset

Os modificadores de padding de inserção (windowInsetsPadding e auxiliares como safeDrawingPadding) consomem automaticamente a parte dos insets que são aplicadas como padding. Ao se aprofundar na árvore de composição, o encarte aninhado modificadores de padding e de tamanho do encarte sabem que uma parte dos já foram consumidos por modificadores de padding externos e evitam 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 o uso da mesma parte de encartes mais de uma vez se os encartes já tiverem sido consumidos. No entanto, como eles estão mudando o tamanho diretamente, eles não consomem insets.

Como resultado, o aninhamento de modificadores de padding muda automaticamente a quantidade de padding aplicado a cada elemento combinável.

Analisando o mesmo exemplo de LazyColumn de antes, o LazyColumn está sendo redimensionado pelo modificador imePadding. Dentro do LazyColumn, o último item é dimensionado para ter a altura da parte de baixo das barras do sistema:

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

Quando o IME está 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 será consumido, e a altura do Spacer será o tamanho do na parte de baixo das barras do sistema.

Quando o IME é aberto, os encartes do IME são animados para corresponder ao tamanho do IME, e o O modificador imePadding() começa a aplicar o padding de baixo para redimensionar o LazyColumn quando o IME é aberto. Quando o modificador imePadding() começa a ser aplicado o padding da parte de baixo 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(). Quando o O modificador imePadding() está aplicando uma quantidade maior de padding inferior que as barras do sistema, a altura da Spacer é zero.

Quando o IME é fechado, as mudanças acontecem de forma inversa: o Spacer começa a se expandir a partir de uma altura de zero quando o imePadding() está aplicando menos que a parte de baixo das barras do sistema, até que o Spacer corresponda à altura da parte de baixo das barras do sistema quando o IME é completamente animado.

Figura 2. Coluna lenta de borda a borda com TextField.

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

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

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

O Modifier.consumeWindowInsets(paddingValues: PaddingValues) se comporta de maneira muito semelhante à versão com um argumento WindowInsets, mas consome um PaddingValues arbitrário. Isso é útil para informar elementos filhos quando o padding ou o espaçamento é fornecido por algum outro mecanismo que não seja os modificadores de padding de inserção, como um Modifier.padding comum ou espaçadores de altura fixa:

Column(Modifier.padding(16.dp).consumeWindowInsets(PaddingValues(16.dp))) {
    // content
    Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime))
}

Nos casos em que os encartes de janela brutos forem necessários sem consumo, use o método 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, prefira usar o padding de encartes de janela. e encarte de janela sempre que possível.

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 subjacentes para gerenciar encartes. Devido a esse comportamento da plataforma, os insets têm uma relação especial com as fases do Jetpack Compose.

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

Animações de IME do teclado com WindowInsets

Você pode aplicar Modifier.imeNestedScroll() a um contêiner de rolagem para abrir e feche o IME automaticamente ao rolar para a parte inferior 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 3. Animações do IME.

Suporte a encarte para os componentes do Material 3

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

Elementos combináveis que processam o recuo

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

Barras de apps

Contêineres de conteúdo

Scaffold

Por padrão, Scaffold fornece insets 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 insetos 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 insetos padrão

É possível mudar o parâmetro windowInsets transmitido ao elemento combinável para configurar o comportamento da função de composição. Esse parâmetro pode ser um tipo diferente de encarte de janela para aplicar ou desativado ao transmitir 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

Talvez seja necessário substituir os insetos padrão quando a tela tiver visualizações e código do Compose na mesma hierarquia. Nesse caso, é necessário especificar qual deles deve consumir os insetos e qual deve ignorá-los.

Por exemplo, se o layout mais externo for um layout de visualização do Android, você deverá consumir os encartes no sistema de visualização e ignorá-los no Compose. Como alternativa, se o layout mais externo for um elemento combinável, consuma as incrustações no Compose e adicione os elementos combináveis AndroidView.

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