Usando WindowInsetsCompat,
seu app pode consultar e controlar o teclado na tela (também chamado de
IME) de forma semelhante
à maneira como ele interage com as barras de sistema. O app também pode usar
WindowInsetsAnimationCompat
para criar transições perfeitas quando o teclado de software é aberto ou fechado.
Pré-requisitos
Antes de configurar o controle e a animação do teclado de software, configure o app para exibição de ponta a ponta. Isso permite que ele processe inserções de janela do sistema, como as barras de sistema e o teclado na tela.
Animação de encartes sincronizados padrão
A partir do nível 37.2 da API, os aplicativos que usam os ajustes padrão de layout de janela (como SOFT_INPUT_ADJUST_RESIZE ou SOFT_INPUT_ADJUST_PAN ao
mostrar o IME usando WindowInsetsController.show(WindowInsets.Type.ime()))
sem implementar um WindowInsetsAnimationCompat.Callback personalizado podem se beneficiar
de animações sincronizadas quadro a quadro no nível do sistema. Quando ativado, esse
recurso aplica automaticamente as inserções em cada frame e aciona uma passagem de
layout, criando uma transição suave conforme o teclado se move.
Para ativar esse comportamento, adicione a seguinte tag <property> à atividade ou
ao aplicativo no arquivo AndroidManifest.xml:
<property
android:name="android.window.PROPERTY_COMPAT_ALLOW_SYNCHRONIZED_INSETS_ANIMATION"
android:value="true" />
Realizar animações de encartes fora da linha de execução principal
Por padrão, o registro de um
WindowInsetsAnimationCompat.Callback garante que a
animação de inserção de janela seja executada na linha de execução principal do app. Isso é necessário se o
app estiver atualizando e sincronizando continuamente o layout de visualização
durante a animação (como no método onProgress). No entanto, se o app
precisar apenas detectar transições de ciclo de vida (como quando o IME inicia ou
encerra a animação) e não precisar atualizar as visualizações frame a frame, a execução na
linha de execução principal poderá causar sobrecarga desnecessária e travamento da animação, especialmente se
a linha de execução principal estiver ocupada.
A partir do nível 37.2 da API, use
ViewTreeObserver.WindowInsetsAnimationListener
para observar essas transições. Como esse listener não recebe
atualizações de progresso frame a frame (sem callback onProgress), o sistema pode executar
a animação de inserção em uma linha de execução dedicada, em vez da linha de execução principal,
resultando em uma transição mais suave.
Kotlin
val listener = object : ViewTreeObserver.WindowInsetsAnimationListener { override fun onPrepare(animation: WindowInsetsAnimation) { // Handle preparation before animation starts } override fun onEnd(animation: WindowInsetsAnimation) { // Clean up temporary changes } } // Add the listener view.viewTreeObserver.addWindowInsetsAnimationListener(listener) // Remove the listener when no longer needed view.viewTreeObserver.removeWindowInsetsAnimationListener(listener)
Java
ViewTreeObserver.WindowInsetsAnimationListener listener = new ViewTreeObserver.WindowInsetsAnimationListener() { @Override public void onPrepare(@NonNull WindowInsetsAnimation animation) { // Handle preparation before animation starts } @Override public void onEnd(@NonNull WindowInsetsAnimation animation) { // Clean up temporary changes } }; // Add the listener view.getViewTreeObserver().addWindowInsetsAnimationListener(listener); // Remove the listener when no longer needed view.getViewTreeObserver().removeWindowInsetsAnimationListener(listener);
Verificar a visibilidade do software do teclado
Use WindowInsets para verificar a visibilidade do teclado
de 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 na parte de baixo da tela, como mostrado no exemplo a seguir:
O exemplo rotulado como "Não sincronizado" 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 se ajustam ao lugar em vez de sincronizar com a animação do teclado, um comportamento que pode ser visualmente desagradável.
No Android 11 (nível 30 da API) e versões mais recentes, é possível usar
WindowInsetsAnimationCompatpara sincronizar a transição do app com o teclado deslizando para cima e para baixo na parte de baixo da tela. Isso parece mais suave, como mostrado no exemplo rotulado como "Synchronized" na Figura 2.
Configure
WindowInsetsAnimationCompat.Callback
com a visualização a ser 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 encartes está começando e antes que as visualizações
sejam reorganizadas devido a uma animação. Você pode usá-lo para salvar o estado inicial,
que, neste caso, é a coordenada inferior da visualização.
onPrepare() para
registrar o estado inicial.
O snippet a seguir 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 encartes é iniciada. Você pode usá-lo para definir todas
as propriedades de visualização para o estado final das mudanças de layout. Se você tiver um
callback OnApplyWindowInsetsListener definido para qualquer uma das visualizações, ele já será
chamado nesse momento. Este é um bom momento para salvar o estado final das propriedades
da visualização.
onStart() para registrar
o estado final.
O snippet a seguir 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 as inserções mudam como parte da execução de uma animação.
Assim, é possível substituí-lo e receber notificações em cada frame durante a animação
do teclado. Atualize as propriedades da visualização para que ela seja animada em
sincronia com o teclado.
Todas as mudanças de layout foram concluídas. Por exemplo, se você usar
View.translationY para deslocar a visualização, o valor vai diminuir gradualmente a cada
chamada desse método e, por fim, vai chegar a 0 na posição original do layout.
onProgress() para
sincronizar as animações.
O snippet a seguir 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 quiser, é possível substituir onEnd. Esse método é chamado após o término da
animação. Este é um bom momento para limpar as alterações temporárias.
Outros recursos
- WindowInsetsAnimation no GitHub.