Cómo controlar y animar el teclado en pantalla

Con WindowInsetsCompat, tu app puede consultar y controlar el teclado en pantalla (también llamado IME) de manera similar al cómo interactúa con las barras del sistema. Tu app también puede usar WindowInsetsAnimationCompat para crear transiciones fluidas cuando se abre o se cierra el teclado en pantalla.

Figura 1: Dos ejemplos del teclado en pantalla una transición abierta y cerrada.

Requisitos previos

Antes de configurar el control y la animación para el teclado en pantalla, configura tu app para mostrar de borde a borde. Esto permite Este controla las inserciones de ventana del sistema, como las barras del sistema y el teclado en pantalla.

Cómo comprobar la visibilidad del software del teclado

Usa WindowInsets para verificar el software. visibilidad del teclado.

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, puedes usar ViewCompat.setOnApplyWindowInsetsListener para observar los cambios en la visibilidad del teclado en pantalla.

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

Cómo sincronizar la animación con el teclado en pantalla

Un usuario que toca un campo de entrada de texto hace que el teclado se deslice en su lugar desde la parte inferior de la pantalla, como se muestra en el siguiente ejemplo:

Figura 2: Animación de teclado sincronizado.
  • El ejemplo etiquetado como "Sin sincronizar" de la figura 2, se muestra el comportamiento predeterminado en Android 10 (nivel de API 29), ya que el campo de texto y el contenido de la app en lugar de sincronizarse con el teclado animación, comportamiento que puede ser visualmente molesto.

  • En Android 11 (nivel de API 30) y versiones posteriores, puedes usar WindowInsetsAnimationCompat para sincronizar la transición de la app con el teclado deslizándose hacia arriba y hacia abajo desde la parte inferior de la pantalla. Esto parece más fluida, como se muestra en el ejemplo etiquetado como "Sincronizada" en la figura 2.

Configurar WindowInsetsAnimationCompat.Callback que la vista se sincronice con la animación del 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.
    });

Existen varios métodos para anular en WindowInsetsAnimationCompat.Callback. es decir, onPrepare(), onStart(), onProgress(), y onEnd(). Comienza llamando a onPrepare() antes de que cambie el diseño.

Se llama a onPrepare cuando se inicia una animación de inserciones y antes de las vistas. se vuelven a colocar debido a una animación. Puedes usarlo para guardar el estado de inicio, que en este caso es la coordenada inferior de la vista.

Una imagen que muestra la coordenada inferior del estado de inicio de la vista raíz.
Figura 3: Se usa onPrepare() para registrar el estado de inicio.

En el siguiente fragmento, se muestra una llamada de ejemplo a 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();
}

Se llama a onStart cuando comienza una animación de inserciones. Puedes usarlo para establecer las propiedades de vista hasta el estado final de los cambios de diseño. Si tienes un La devolución de llamada OnApplyWindowInsetsListener se estableció en cualquiera de las vistas; ya está llamada en este punto. Este es un buen momento para guardar el estado final de la vista propiedades.

Una imagen que muestra la coordenada inferior del estado final de la vista
Figura 4: Usando onStart() para grabar el estado final.

En el siguiente fragmento, se muestra una llamada de ejemplo a 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;
}

Se llama a onProgress cuando las inserciones cambian como parte de la ejecución de una animación. para que puedas anularla y recibir notificaciones en cada fotograma durante el teclado animación. Actualiza las propiedades de la vista para que esta se anime en y la sincronización con el teclado.

Todos los cambios de diseño están completos en este punto. Por ejemplo, si utilizas View.translationY para cambiar la vista, el valor disminuye gradualmente para cada llamada de este método y, finalmente, alcanza 0 en la posición original del diseño.

Figura 5: Se usa onProgress() para para sincronizar las animaciones.

En el siguiente fragmento, se muestra una llamada de ejemplo a 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;
}

De manera opcional, puedes anular onEnd. Se llama a este método después de la animación antes de que termine el período. Este es un buen momento para limpiar los cambios temporales.

Recursos adicionales