Softwaretastatur steuern und animieren

Compose ausprobieren
Jetpack Compose ist das empfohlene UI-Toolkit für Android. Informationen zur Verwendung der Tastatur in Compose

Mit WindowInsetsCompat kann deine App die Bildschirmtastatur (auch IME genannt) ähnlich wie die Systemleisten abfragen und steuern. Ihre App kann auch WindowInsetsAnimationCompat verwenden, um nahtlose Übergänge zu erstellen, wenn die Softwaretastatur geöffnet oder geschlossen wird.

Abbildung 1. Zwei Beispiele für den Übergang zwischen geöffneter und geschlossener Bildschirmtastatur.

Vorbereitung

Bevor Sie die Steuerung und Animation für die Bildschirmtastatur einrichten, müssen Sie Ihre App so konfigurieren, dass sie im Vollbildmodus angezeigt wird. So können Systemfenster-Insets wie die Systemleisten und die Bildschirmtastatur verarbeitet werden.

Standardanimation für synchronisierte Insets

Ab API-Level 37.2 können Anwendungen, die die Standardanpassungen des Fensterlayouts verwenden (z. B. SOFT_INPUT_ADJUST_RESIZE oder SOFT_INPUT_ADJUST_PAN, wenn die IME mit WindowInsetsController.show(WindowInsets.Type.ime()) angezeigt wird), ohne Implementierung einer benutzerdefinierten WindowInsetsAnimationCompat.Callback von synchronisierten Frame-by-Frame-Animationen auf Systemebene profitieren. Wenn diese Funktion aktiviert ist, werden die Insets automatisch auf jeden Frame angewendet und ein Layoutdurchlauf wird ausgelöst. So entsteht ein flüssiger Übergang, wenn sich die Tastatur bewegt.

Wenn Sie dieses Verhalten aktivieren möchten, fügen Sie Ihrer Aktivität oder Anwendung in der Datei AndroidManifest.xml das folgende <property>-Tag hinzu:

<property
    android:name="android.window.PROPERTY_COMPAT_ALLOW_SYNCHRONIZED_INSETS_ANIMATION"
    android:value="true" />

Animationen für Einzüge außerhalb des Hauptthreads ausführen

Wenn Sie einen WindowInsetsAnimationCompat.Callback registrieren, wird die Animation für den Fenster-Inset standardmäßig im Hauptthread der App ausgeführt. Dies ist erforderlich, wenn Ihre App das Ansichtslayout während der Animation kontinuierlich aktualisiert und synchronisiert (z. B. in der Methode onProgress). Wenn deine App jedoch nur auf Lebenszyklusübergänge warten muss (z. B. wenn die IME mit der Animation beginnt oder sie beendet) und die Ansichten nicht Frame für Frame aktualisieren muss, kann die Ausführung im Hauptthread zu unnötigem Overhead und Ruckeln der Animation führen, insbesondere wenn der Hauptthread anderweitig beschäftigt ist.

Ab API-Ebene 37.2 können Sie stattdessen ViewTreeObserver.WindowInsetsAnimationListener verwenden, um diese Übergänge zu beobachten. Da dieser Listener keine Frame-für-Frame-Fortschrittsaktualisierungen empfängt (kein onProgress-Callback), kann das System die Inset-Animation in einem dedizierten Animations-Thread statt im Haupt-Thread ausführen, was zu einem flüssigeren Übergang führt.

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

Sichtbarkeit der Tastatursoftware prüfen

Verwenden Sie WindowInsets, um die Sichtbarkeit der Bildschirmtastatur zu prüfen.

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;

Alternativ können Sie ViewCompat.setOnApplyWindowInsetsListener verwenden, um Änderungen an der Sichtbarkeit der Bildschirmtastatur zu beobachten.

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

Animation mit der Softwaretastatur synchronisieren

Wenn ein Nutzer auf ein Texteingabefeld tippt, wird die Tastatur von unten auf dem Bildschirm eingeblendet, wie im folgenden Beispiel gezeigt:

Abbildung 2. Animation einer synchronisierten Tastatur.
  • Das Beispiel mit der Bezeichnung „Nicht synchronisiert“ in Abbildung 2 zeigt das Standardverhalten in Android 10 (API-Level 29). Dabei rasten das Textfeld und der Inhalt der App ein, anstatt sich mit der Animation der Tastatur zu synchronisieren. Dieses Verhalten kann visuell störend sein.

  • In Android 11 (API-Level 30) und höher kannst du WindowInsetsAnimationCompat verwenden, um den Übergang der App mit dem Ein- und Ausblenden der Tastatur vom unteren Displayrand zu synchronisieren. Das sieht flüssiger aus, wie im Beispiel „Synchronisiert“ in Abbildung 2 zu sehen ist.

Konfiguriere WindowInsetsAnimationCompat.Callback mit der Ansicht, die mit der Tastaturanimation synchronisiert werden soll.

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

In WindowInsetsAnimationCompat.Callback gibt es mehrere Methoden, die überschrieben werden können, nämlich onPrepare(), onStart(), onProgress() und onEnd(). Rufen Sie zuerst onPrepare() auf, bevor Sie Layoutänderungen vornehmen.

onPrepare wird aufgerufen, wenn eine Insets-Animation gestartet wird und bevor die Ansichten aufgrund einer Animation neu angeordnet werden. Damit können Sie den Startzustand speichern, in diesem Fall die untere Koordinate der Ansicht.

Ein Bild, das die untere Koordinate des Startzustands der Stammansicht zeigt.
Abbildung 3. Verwenden Sie onPrepare(), um den Startzustand aufzuzeichnen.

Das folgende Snippet zeigt einen Beispielaufruf von 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 wird aufgerufen, wenn eine Insets-Animation beginnt. Damit können Sie alle Ansichtseigenschaften auf den Endzustand der Layoutänderungen festlegen. Wenn Sie einen OnApplyWindowInsetsListener-Callback für eine der Ansichten festgelegt haben, wird er an dieser Stelle bereits aufgerufen. Jetzt ist ein guter Zeitpunkt, um den Endzustand der Ansichtseigenschaften zu speichern.

Ein Bild, das die untere Koordinate des Endzustands der Ansicht zeigt
Abbildung 4. Verwenden Sie onStart(), um den Endzustand aufzuzeichnen.

Das folgende Snippet zeigt einen Beispielaufruf von 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 wird aufgerufen, wenn sich die Insets im Rahmen einer Animation ändern. Du kannst die Methode also überschreiben und wirst dann bei jedem Frame während der Tastaturanimation benachrichtigt. Aktualisieren Sie die Ansichtseigenschaften, damit die Ansicht synchron mit der Tastatur animiert wird.

Alle Layoutänderungen sind jetzt abgeschlossen. Wenn Sie beispielsweise View.translationY verwenden, um die Ansicht zu verschieben, verringert sich der Wert bei jedem Aufruf dieser Methode schrittweise und erreicht schließlich 0 zur ursprünglichen Layoutposition.

Abbildung 5: Verwendung von onProgress() zum Synchronisieren der Animationen.

Das folgende Snippet zeigt einen Beispielaufruf von 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;
}

Optional können Sie onEnd überschreiben. Diese Methode wird aufgerufen, nachdem die Animation beendet wurde. Dies ist ein guter Zeitpunkt, um temporäre Änderungen zu bereinigen.

Zusätzliche Ressourcen