Softwaretastatur steuern und animieren

Compose ausprobieren
Jetpack Compose ist das empfohlene UI-Toolkit für Android. Hier erfahren Sie, wie Sie mit der Tastatur in Compose arbeiten.

Mit WindowInsetsCompat kann Ihre App die Bildschirmtastatur (auch IME genannt) abfragen und steuern, ähnlich wie sie mit den Systemleisten interagiert. 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öffnetem und geschlossenem Software-Keyboard.

Vorbereitung

Bevor Sie die Steuerung und Animation für die Softwaretastatur einrichten, konfigurieren Sie Ihre App so, dass sie randlos angezeigt wird. Dadurch kann es Systemfenstereinsätze wie die Systemleisten und die Bildschirmtastatur verarbeiten.

Standardanimation für synchronisierte Insets

Ab API-Level 37.2 und höher können Anwendungen, die die standardmäßigen Anpassungen des Fensterlayouts verwenden (z. B. SOFT_INPUT_ADJUST_RESIZE oder SOFT_INPUT_ADJUST_PAN beim Anzeigen der IME mit WindowInsetsController.show(WindowInsets.Type.ime())), ohne eine benutzerdefinierte WindowInsetsAnimationCompat.Callback zu implementieren, von synchronisierten Frame-by-Frame-Animationen auf Systemebene profitieren. Wenn diese Funktion aktiviert ist, werden die Insets automatisch auf jeden Frame angewendet und es wird ein Layoutdurchlauf ausgelöst, wodurch ein reibungsloser Übergang entsteht, wenn sich die Tastatur bewegt.

Fügen Sie das folgende <property>-Tag in der Datei AndroidManifest.xml in Ihre Aktivität oder Anwendung ein, um dieses Verhalten zu aktivieren:

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

Inset-Animationen außerhalb des Haupt-Threads ausführen

Wenn Sie ein WindowInsetsAnimationCompat.Callback registrieren, wird standardmäßig erzwungen, dass die Animation für das Einblenden des Fensters im Hauptthread der App ausgeführt wird. Dies ist erforderlich, wenn das Ansichtslayout Ihrer App während der Animation kontinuierlich aktualisiert und synchronisiert wird (z. B. in der Methode onProgress). Wenn Ihre App jedoch nur auf Lebenszyklusübergänge reagieren muss (z. B. wenn das IME mit der Animation beginnt oder sie beendet) und Ansichten nicht Frame für Frame aktualisiert werden müssen, kann die Ausführung im Hauptthread unnötigen Overhead und Animationsruckeln verursachen, 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-by-Frame-Fortschrittsaktualisierungen (kein onProgress-Callback) empfängt, kann das System die Inset-Animation in einem dedizierten Animations-Thread anstelle des Hauptthreads 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

Mit WindowInsets können Sie prüfen, ob die Softwaretastatur sichtbar ist.

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 Softwaretastatur 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 vom unteren Bildschirmrand eingeblendet, wie im folgenden Beispiel gezeigt:

Abbildung 2. Synchronisierte Tastaturanimation.
  • Das Beispiel mit der Bezeichnung „Nicht synchronisiert“ in Abbildung 2 zeigt das Standardverhalten in Android 10 (API-Level 29). Hier werden das Textfeld und der Inhalt der App an die richtige Position gebracht, anstatt mit der Animation der Tastatur synchronisiert zu werden. Das kann visuell störend sein.

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

Konfigurieren Sie WindowInsetsAnimationCompat.Callback so, dass sie mit der Tastaturanimation synchronisiert wird.

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 Änderungen am Layout vornehmen.

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

Ein Bild, das die untere Koordinate des Startstatus der Root-Ansicht zeigt.
Abbildung 3. onPrepare() verwenden, um den Startstatus aufzuzeichnen.

Das folgende Snippet zeigt ein Beispiel für einen Aufruf 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 Endstatus der Layoutänderungen festlegen. Wenn Sie für eine der Ansichten einen OnApplyWindowInsetsListener-Callback festgelegt haben, wird er bereits an dieser Stelle aufgerufen. Jetzt ist ein guter Zeitpunkt, um den Endstatus der Ansichtseigenschaften zu speichern.

Ein Bild mit der unteren Koordinate des Endzustands der Ansicht
Abbildung 4. onStart() verwenden, um den Endstatus aufzuzeichnen.

Das folgende Snippet zeigt ein Beispiel für einen Aufruf 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. Sie können die Methode also überschreiben und bei jedem Frame während der Tastaturanimation benachrichtigt werden. Aktualisieren Sie die Ansichtseigenschaften so, dass 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, nimmt der Wert bei jedem Aufruf dieser Methode allmählich ab und erreicht schließlich 0 für die ursprüngliche Layoutposition.

Abbildung 5. Verwenden Sie onProgress(), um die Animationen zu synchronisieren.

Das folgende Snippet zeigt ein Beispiel für einen Aufruf 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 nach Abschluss der Animation aufgerufen. Jetzt ist ein guter Zeitpunkt, um temporäre Änderungen zu entfernen.

Zusätzliche Ressourcen