Entender encartes de janela no WebView

A WebView gerencia o alinhamento de conteúdo usando duas janelas de visualização: a janela de layout (o tamanho da página) e a janela visual (a parte da página que o usuário vê). Embora a janela de visualização de layout seja geralmente estática, a janela de visualização visual muda dinamicamente quando os usuários fazem zoom, rolam ou quando elementos da interface do sistema (como o teclado de software) aparecem.

Compatibilidade de recursos

O suporte do WebView para encartes de janela evoluiu ao longo do tempo para alinhar o comportamento do conteúdo da Web com as expectativas nativas de apps Android:

Marco Recurso adicionado Escopo
M136 displayCutout() e systemBars() são compatíveis com safe-area-insets do CSS. Somente WebViews em tela cheia.
M139 ime() (editor de método de entrada, que é um teclado) com redimensionamento visual da janela de visualização. Todos os WebViews.
M144 Compatibilidade com displayCutout() e systemBars(). Todas as WebViews (independente do estado de tela cheia).

Para mais informações, consulte WindowInsetsCompat.

Mecânica principal

O WebView processa encartes por dois mecanismos principais:

  • Áreas seguras (displayCutout, systemBars): a WebView encaminha essas dimensões para o conteúdo da Web usando variáveis CSS safe-area-inset-*. Isso permite que os desenvolvedores evitem que os próprios elementos interativos (como barras de navegação) sejam ocultados por entalhes ou barras de status.

  • Redimensionamento visual da janela de visualização usando o editor de método de entrada (IME): a partir do M139, o editor de método de entrada (IME) redimensiona diretamente a janela de visualização visual. Esse mecanismo de redimensionamento também se baseia na interseção WebView-Window. Por exemplo, no modo multitarefa do Android, se a parte de baixo de uma WebView se estender 200 dp abaixo da parte de baixo da janela, a janela de visualização visual será 200 dp menor que o tamanho da WebView. Esse redimensionamento da janela de visualização (para IME e interseção WebView-Window) só é aplicado à parte de baixo da WebView. Esse mecanismo não oferece suporte ao redimensionamento para sobreposição à esquerda, à direita ou na parte de cima. Isso significa que os teclados encaixados que aparecem nessas bordas não acionam um redimensionamento visual da janela de visualização.

Antes, a janela de visualização visual permanecia fixa, muitas vezes ocultando campos de entrada atrás do teclado. Ao redimensionar a janela de visualização, a parte visível da página se torna rolável por padrão, garantindo que os usuários possam acessar o conteúdo oculto.

Lógica de limites e sobreposição

A WebView só deve receber valores de encarte diferentes de zero quando elementos da interface do sistema (barras, cortes de tela ou teclado) se sobrepõem diretamente aos limites da tela da WebView. Se um WebView não se sobrepuser a esses elementos de UI (por exemplo, se um WebView estiver centralizado na tela e não tocar nas barras de sistema), ele vai receber esses encartes como zero.

Para substituir essa lógica padrão e fornecer o conteúdo da Web com as dimensões completas do sistema, independente da sobreposição, use o método setOnApplyWindowInsetsListener e retorne o objeto windowInsets original e não modificado do listener. Fornecer dimensões completas do sistema pode ajudar a garantir a consistência do design, permitindo que o conteúdo da Web se alinhe ao hardware do dispositivo, independente da posição atual da WebView. Isso garante uma transição suave à medida que a WebView se move ou se expande para tocar nas bordas da tela.

Kotlin

ViewCompat.setOnApplyWindowInsetsListener(myWebView) { _, windowInsets ->
    // By returning the original windowInsets object, we override the default
    // behavior that zeroes out system insets (like system bars or display
    // cutouts) when they don't directly overlap the WebView's screen bounds.
    windowInsets
}

Java

ViewCompat.setOnApplyWindowInsetsListener(myWebView, (v, windowInsets) -> {
  // By returning the original windowInsets object, we override the default
  // behavior that zeroes out system insets (like system bars or display
  // cutouts) when they don't directly overlap the WebView's screen bounds.
  return windowInsets;
});

Gerenciar eventos de redimensionamento

Como a visibilidade do teclado agora aciona um redimensionamento da janela de visualização visual, o código da Web pode ver eventos de redimensionamento mais frequentes. Os desenvolvedores precisam garantir que o código não reaja a esses eventos de redimensionamento limpando o foco do elemento. Isso cria um loop de perda de foco e dispensa do teclado que impede a entrada do usuário:

  1. O usuário se concentra em um elemento de entrada.
  2. O teclado aparece, acionando um evento de redimensionamento.
  3. O código do site limpa o foco em resposta ao redimensionamento.
  4. O teclado é ocultado porque o foco foi perdido.

Para reduzir esse comportamento, revise os listeners do lado da Web e garanta que as mudanças na viewport não acionem sem querer a função JavaScript blur() ou comportamentos de limpeza de foco.

Implementar o processamento de encartes

As configurações padrão da WebView funcionam automaticamente para a maioria dos apps. No entanto, se o app usar layouts personalizados (por exemplo, se você adicionar seu próprio padding para considerar a barra de status ou o teclado), use as abordagens a seguir para melhorar a forma como o conteúdo da Web e a interface nativa funcionam juntos. Se a interface nativa aplicar padding a um contêiner com base em WindowInsets, gerencie esses encartes corretamente antes que eles cheguem ao WebView para evitar padding duplo.

O padding duplo ocorre quando o layout nativo e o conteúdo da Web aplicam as mesmas dimensões de encarte, resultando em espaçamento redundante. Por exemplo, imagine um smartphone com uma barra de status de 40 px. Tanto a visualização nativa quanto a WebView veem o encarte de 40 px. Ambos adicionam 40 px de padding, resultando em uma lacuna de 80 px na parte de cima.

A abordagem de zeragem

Para evitar o padding duplo, garanta que, depois que uma visualização nativa usar uma dimensão de encarte para padding, você redefina essa dimensão para zero usando Insets.NONE em um novo objeto WindowInsets antes de transmitir o objeto modificado pela hierarquia de visualização para a WebView.

Ao aplicar padding a uma visualização principal, geralmente é melhor usar a abordagem de zeragem definindo Insets.NONE em vez de WindowInsetsCompat.CONSUMED. Retornar WindowInsetsCompat.CONSUMED pode funcionar em determinadas situações. No entanto, ele pode ter problemas se o manipulador do app mudar encartes ou adicionar padding próprio. A abordagem de zeragem não tem essas limitações.

Evitar padding fantasma zerando encartes

Se você consumir os encartes quando o app tiver transmitido encartes não consumidos anteriormente ou se os encartes mudarem (como o teclado sendo ocultado), o consumo vai impedir que a WebView receba a notificação de atualização necessária. Isso pode fazer com que a WebView mantenha o padding fantasma de um estado anterior (por exemplo, mantendo o padding do teclado depois que ele é ocultado).

O exemplo a seguir mostra uma interação interrompida entre o app e a WebView:

  1. Estado inicial:o app transmite inicialmente encartes não consumidos (por exemplo, displayCutout() ou systemBars()) para o WebView, que aplica internamente o padding ao conteúdo da Web.
  2. Mudança de estado e erro:se o app mudar de estado (por exemplo, o teclado for ocultado) e escolher processar os encartes resultantes retornando WindowInsetsCompat.CONSUMED.
  3. Notificação bloqueada:o consumo de encartes impede que o sistema Android envie a notificação de atualização necessária pela hierarquia de visualização para a WebView.
  4. Padding fantasma:como a WebView não recebe a atualização, ela retém o padding do estado anterior, causando um padding fantasma (por exemplo, manter o padding do teclado depois que ele é ocultado).

Em vez disso, use WindowInsetsCompat.Builder para definir os tipos processados como zero antes de transmitir o objeto para as visualizações secundárias. Isso informa à WebView que esses encartes específicos já foram considerados ao permitir que a notificação continue na hierarquia de visualização.

Kotlin

ViewCompat.setOnApplyWindowInsetsListener(rootView) { view, windowInsets ->
    // 1. Identify the inset types you want to handle natively
    val types = WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout()

    // 2. Extract the dimensions and apply them as padding to the native container
    val insets = windowInsets.getInsets(types)
    view.setPadding(insets.left, insets.top, insets.right, insets.bottom)

    // 3. Return a new WindowInsets object with the handled types set to NONE (zeroed).
    // This informs the WebView that these areas are already padded, preventing
    // double-padding while still allowing the WebView to update its internal state.
    WindowInsetsCompat.Builder(windowInsets)
        .setInsets(types, Insets.NONE)
        .build()
}

Java

ViewCompat.setOnApplyWindowInsetsListener(rootView, (view, windowInsets) -> {
  // 1. Identify the inset types you want to handle natively
  int types = WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout();

  // 2. Extract the dimensions and apply them as padding to the native container
  Insets insets = windowInsets.getInsets(types);
  rootView.setPadding(insets.left, insets.top, insets.right, insets.bottom);

  // 3. Return a new Insets object with the handled types set to NONE (zeroed).
  // This informs the WebView that these areas are already padded, preventing
  // double-padding while still allowing the WebView to update its internal
  // state.
  return new WindowInsetsCompat.Builder(windowInsets)
    .setInsets(types, Insets.NONE)
    .build();
});

Como deixar de participar

Para desativar esses comportamentos modernos e voltar ao processamento de viewport legado, faça o seguinte:

  1. Interceptar encartes:use setOnApplyWindowInsetsListener ou substitua onApplyWindowInsets em uma subclasse WebView.

  2. Limpar encartes:retorna um conjunto consumido de encartes (por exemplo, WindowInsetsCompat.CONSUMED) desde o início. Essa ação impede que a notificação de encarte se propague para a WebView, desativando o redimensionamento moderno da janela de visualização e forçando a WebView a manter o tamanho inicial da janela de visualização.