Contrôler et animer le clavier virtuel

À l'aide de WindowInsetsCompat, votre application peut interroger et contrôler le clavier à l'écran (également appelé IME) de la même manière qu'elle interagit avec les barres système. Votre application peut également utiliser WindowInsetsAnimationCompat pour créer des transitions fluides lorsque le clavier virtuel est ouvert ou fermé.

Figure 1. Deux exemples de transition ouverte-fermée du clavier virtuel

Conditions préalables

Avant de configurer les commandes et les animations du clavier virtuel, configurez votre application pour qu'elle affiche l'écran bord à bord. Cela lui permet de gérer les encarts de fenêtre système tels que les barres système et le clavier à l'écran.

Vérifier la visibilité du logiciel du clavier

Utilisez WindowInsets pour vérifier la visibilité du clavier virtuel.

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;

Vous pouvez également utiliser ViewCompat.setOnApplyWindowInsetsListener pour observer les modifications apportées à la visibilité du clavier virtuel.

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

Synchroniser l'animation avec le clavier virtuel

Lorsqu'un utilisateur appuie sur un champ de saisie de texte, le clavier glisse en place à partir du bas de l'écran, comme illustré dans l'exemple suivant:

Figure 2. Animation synchronisée au clavier.
  • L'exemple intitulé "Désynchronisé" à la figure 2 illustre le comportement par défaut d'Android 10 (niveau d'API 29), dans lequel le champ de texte et le contenu de l'application sont alignés au lieu de se synchroniser avec l'animation du clavier. Ce comportement peut être visuellement troublant.

  • Sous Android 11 (niveau d'API 30) ou version ultérieure, vous pouvez utiliser WindowInsetsAnimationCompat pour synchroniser la transition de l'application avec le clavier glissant du bas de l'écran vers le haut ou vers le bas. Cela semble plus fluide, comme illustré dans l'exemple intitulé "Synchronisé" de la figure 2.

Configurez WindowInsetsAnimationCompat.Callback avec la vue à synchroniser avec l'animation du clavier.

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

Il existe plusieurs méthodes à remplacer dans WindowInsetsAnimationCompat.Callback, à savoir onPrepare(), onStart(), onProgress() et onEnd(). Commencez par appeler onPrepare() avant toute modification de la mise en page.

onPrepare est appelé lorsqu'une animation d'encarts commence et avant que les vues ne soient redisposées en raison d'une animation. Vous pouvez l'utiliser pour enregistrer l'état de début, qui, dans ce cas, correspond à la coordonnée inférieure de la vue.

Image montrant la coordonnée inférieure de l'état de départ de la vue racine.
Figure 3 : Utilisation de onPrepare() pour enregistrer l'état de début.

L'extrait de code suivant présente un exemple d'appel à 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 est appelé lorsqu'une animation d'encarts commence. Vous pouvez l'utiliser pour définir toutes les propriétés de la vue sur l'état final des modifications de mise en page. Si un rappel OnApplyWindowInsetsListener est défini sur l'une des vues, il est déjà appelé à ce stade. C'est le bon moment pour enregistrer l'état final des propriétés de la vue.

Image montrant la coordonnée inférieure de l'état final de la vue
Figure 4 : Utilisation de onStart() pour enregistrer l'état final.

L'extrait de code suivant présente un exemple d'appel à 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 est appelé lorsque les encarts changent dans le cadre de l'exécution d'une animation. Vous pouvez donc le remplacer et être averti sur chaque frame pendant l'animation au clavier. Mettez à jour les propriétés de la vue afin qu'elle s'anime lors de la synchronisation avec le clavier.

Toutes les modifications de mise en page sont terminées. Par exemple, si vous utilisez View.translationY pour déplacer la vue, la valeur diminue progressivement pour chaque appel de cette méthode et atteint 0 à la position d'origine de la mise en page.

Figure 5. Utilisez onProgress() pour synchroniser les animations.

L'extrait de code suivant présente un exemple d'appel à 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;
}

Vous pouvez éventuellement ignorer onEnd. Cette méthode est appelée une fois l'animation terminée. C'est le bon moment pour nettoyer tous les changements temporaires.

Ressources supplémentaires