Controlla e anima la tastiera software

Con WindowInsetsCompat, la tua app può eseguire query e controllare la tastiera sullo schermo (chiamata anche IME) in modo simile al modo in cui interagisce con le barre di sistema. La tua app può usare anche WindowInsetsAnimationCompat per creare transizioni fluide all'apertura o alla chiusura della tastiera software.

Figura 1. Due esempi di transizione dalla tastiera software aperta e chiusa.

Prerequisiti

Prima di impostare il controllo e l'animazione per la tastiera software, configura l'app in modo che mostri la visualizzazione edge-to-edge. In questo modo può gestire gli insiemi di finestre di sistema, come le barre di sistema e la tastiera sullo schermo.

Controllare la visibilità del software della tastiera

Usa WindowInsets per controllare la visibilità della tastiera del 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;

In alternativa, puoi utilizzare ViewCompat.setOnApplyWindowInsetsListener per osservare le modifiche alla visibilità della tastiera 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;
});

Sincronizza l'animazione con la tastiera software

Un utente che tocca un campo di immissione di testo fa scorrere la tastiera in posizione dalla parte inferiore dello schermo, come mostrato nell'esempio seguente:

Figura 2. Animazione tastiera sincronizzata.
  • L'esempio con l'etichetta "Non sincronizzato" nella figura 2 mostra il comportamento predefinito in Android 10 (livello API 29), in cui il campo di testo e il contenuto dell'app si inseriscono invece di sincronizzarsi con l'animazione della tastiera, un comportamento visivamente fastidioso.

  • In Android 11 (livello API 30) e versioni successive, puoi utilizzare WindowInsetsAnimationCompat per sincronizzare la transizione dell'app con la tastiera che scorre in alto e in basso dalla parte inferiore dello schermo. Questo aspetto sembra più uniforme, come mostrato nell'esempio con la dicitura "Sincronizzato" nella figura 2.

Configura WindowInsetsAnimationCompat.Callback con la vista da sincronizzare con l'animazione della tastiera.

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

Esistono diversi metodi per eseguire l'override in WindowInsetsAnimationCompat.Callback, ovvero onPrepare(), onStart(), onProgress() e onEnd(). Inizia chiamando onPrepare() prima di qualsiasi modifica del layout.

onPrepare viene chiamato all'avvio di un'animazione all'interno dell'inserto e prima che le visualizzazioni vengano nuovamente disposte a causa di un'animazione. Puoi usarlo per salvare lo stato iniziale, che in questo caso è la coordinata inferiore della vista.

Un'immagine che mostra la coordinata inferiore dello stato iniziale della vista radice.
Figura 3. Utilizzo di onPrepare() per registrare lo stato di avvio.

Il seguente snippet mostra una chiamata di esempio 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();
}

onStart viene chiamato all'avvio di un'animazione contenente il riquadro. Puoi usarlo per impostare tutte le proprietà della vista sullo stato finale delle modifiche al layout. Se un callback OnApplyWindowInsetsListener è impostato su una delle viste, a questo punto è già chiamato. È un buon momento per salvare lo stato finale delle proprietà vista.

Un'immagine che mostra la coordinata inferiore dello stato finale della vista
Figura 4. Utilizzo di onStart() per registrare lo stato finale.

Il seguente snippet mostra una chiamata di esempio 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;
}

onProgress viene chiamato quando i riquadri cambiano durante l'esecuzione di un'animazione, quindi puoi sostituirlo e ricevere una notifica su ogni frame durante l'animazione della tastiera. Aggiorna le proprietà di una vista in modo che si anima in sincronizzazione con la tastiera.

A questo punto tutte le modifiche al layout sono completate. Ad esempio, se utilizzi View.translationY per spostare la visualizzazione, il valore diminuisce gradualmente per ogni chiamata a questo metodo e alla fine raggiunge 0 nella posizione del layout originale.

Figura 5. Utilizzo di onProgress() per sincronizzare le animazioni.

Il seguente snippet mostra una chiamata di esempio 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;
}

Se vuoi, puoi eseguire l'override di onEnd. Questo metodo viene chiamato al termine dell'animazione. È il momento giusto per eliminare eventuali modifiche temporanee.

Risorse aggiuntive