Controlar e animar o teclado de software

Usando WindowInsetsCompat, o app pode consultar e controlar o teclado na tela (também chamado de IME), de forma semelhante à maneira como ele interage com as barras do sistema. O app também pode usar WindowInsetsAnimationCompat para criar transições perfeitas quando o teclado de software é aberto ou fechado.

Figura 1. Dois exemplos de transição aberta-fechada do teclado de software.

Pré-requisitos

Antes de configurar o controle e a animação do teclado de software, configure o app para exibir de borda a borda. Isso permite processar insets de janela do sistema, como as barras do sistema e o teclado na tela.

Verificar a visibilidade do teclado de software

Use WindowInsets para verificar a visibilidade do teclado do software.

Kotlin

val insets = ViewCompat.getRootWindowInsets(view) ?: return
val imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime())
val imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom

Java

WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(view);
boolean imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime());
int imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom;

Como alternativa, use ViewCompat.setOnApplyWindowInsetsListener para observar mudanças na visibilidade do teclado de software.

Kotlin

ViewCompat.setOnApplyWindowInsetsListener(view) { _, insets ->
  val imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime())
  val imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
  insets
}

Java

ViewCompat.setOnApplyWindowInsetsListener(view, (v, insets) -> {
  boolean imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime());
  int imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom;
  return insets;
});

Sincronizar a animação com o teclado de software

Quando um usuário toca em um campo de entrada de texto, o teclado desliza para o lugar da parte de baixo da tela, conforme mostrado no exemplo abaixo:

Figura 2. Animação sincronizada do teclado.
  • O exemplo "Desincronizado" na Figura 2 mostra o comportamento padrão no Android 10 (nível 29 da API), em que o campo de texto e o conteúdo do app são fixados no lugar em vez de serem sincronizados com a animação do teclado, um comportamento que pode ser visualmente perturbador.

  • No Android 11 (nível 30 da API) e versões mais recentes, é possível usar WindowInsetsAnimationCompat para sincronizar a transição do app com o teclado deslizando para cima e para baixo da parte de baixo da tela. Isso parece mais suave, como mostrado no exemplo "Sincronizado" na Figura 2.

Configure WindowInsetsAnimationCompat.Callback com a visualização para que ela seja sincronizada com a animação do teclado.

Kotlin

ViewCompat.setWindowInsetsAnimationCallback(
  view,
  object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_STOP) {
    // Override methods.
  }
)

Java

ViewCompat.setWindowInsetsAnimationCallback(
    view,
    new WindowInsetsAnimationCompat.Callback(
        WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_STOP
    ) {
      // Override methods.
    });

Há vários métodos para substituir em WindowInsetsAnimationCompat.Callback, como onPrepare(), onStart(), onProgress() e onEnd(). Comece chamando onPrepare() antes de qualquer mudança no layout.

onPrepare é chamado quando uma animação de inserção está começando e antes que as visualizações sejam dispostas novamente devido a uma animação. Você pode usá-lo para salvar o estado inicial, que, neste caso, é a coordenada inferior da visualização.

Uma imagem mostrando a coordenada de baixo do estado inicial da visualização raiz.
Figura 3. Usando onPrepare() para gravar o estado inicial.

O snippet abaixo mostra um exemplo de chamada para onPrepare:

Kotlin

var startBottom = 0f

override fun onPrepare(
  animation: WindowInsetsAnimationCompat
) {
  startBottom = view.bottom.toFloat()
}

Java

float startBottom;

@Override
public void onPrepare(
    @NonNull WindowInsetsAnimationCompat animation
) {
  startBottom = view.getBottom();
}

onStart é chamado quando uma animação de insetos começa. Você pode usá-lo para definir todas as propriedades de visualização no estado final das mudanças de layout. Se você tiver um callback OnApplyWindowInsetsListener definido para qualquer uma das visualizações, ele já será chamado neste ponto. Este é um bom momento para salvar o estado final das propriedades de visualização.

Uma imagem mostrando a coordenada de baixo do estado final da visualização
Figura 4. Usar onStart() para registrar o estado final.

O snippet abaixo mostra um exemplo de chamada para onStart:

Kotlin

var endBottom = 0f

override fun onStart(
  animation: WindowInsetsAnimationCompat,
  bounds: WindowInsetsAnimationCompat.BoundsCompat
): WindowInsetsAnimationCompat.BoundsCompat {
  // Record the position of the view after the IME transition.
  endBottom = view.bottom.toFloat()

  return bounds
}

Java

float endBottom;

@NonNull
@Override
public WindowInsetsAnimationCompat.BoundsCompat onStart(
    @NonNull WindowInsetsAnimationCompat animation,
    @NonNull WindowInsetsAnimationCompat.BoundsCompat bounds
) {
  endBottom = view.getBottom();
  return bounds;
}

onProgress é chamado quando os insets mudam como parte da execução de uma animação. Assim, você pode substituir e receber notificações em todos os frames durante a animação do teclado. Atualize as propriedades da visualização para que ela seja animada em sincronização com o teclado.

Todas as mudanças de layout foram concluídas. Por exemplo, se você usar View.translationY para mudar a visualização, o valor vai diminuir gradualmente em cada chamada desse método e, por fim, vai chegar a 0 na posição original do layout.

Figura 5. Usar onProgress() para sincronizar as animações.

O snippet abaixo mostra um exemplo de chamada para onProgress:

Kotlin

override fun onProgress(
  insets: WindowInsetsCompat,
  runningAnimations: MutableList<WindowInsetsAnimationCompat>
): WindowInsetsCompat {
  // Find an IME animation.
  val imeAnimation = runningAnimations.find {
    it.typeMask and WindowInsetsCompat.Type.ime() != 0
  } ?: return insets

  // Offset the view based on the interpolated fraction of the IME animation.
  view.translationY =
    (startBottom - endBottom) * (1 - imeAnimation.interpolatedFraction)

  return insets
}

Java

@NonNull
@Override
public WindowInsetsCompat onProgress(
    @NonNull WindowInsetsCompat insets,
    @NonNull List<WindowInsetsAnimationCompat> runningAnimations
) {
  // Find an IME animation.
  WindowInsetsAnimationCompat imeAnimation = null;
  for (WindowInsetsAnimationCompat animation : runningAnimations) {
    if ((animation.getTypeMask() & WindowInsetsCompat.Type.ime()) != 0) {
      imeAnimation = animation;
      break;
    }
  }
  if (imeAnimation != null) {
    // Offset the view based on the interpolated fraction of the IME animation.
    view.setTranslationY((startBottom - endBottom)

        *   (1 - imeAnimation.getInterpolatedFraction()));
  }
  return insets;
}

Se preferir, você pode substituir onEnd. Esse método é chamado depois que a animação termina. Esse é um bom momento para limpar as mudanças temporárias.

Outros recursos