Steruj klawiaturą programową i korzystaj z animacji

Dzięki WindowInsetsCompat aplikacja może wysyłać zapytania do klawiatury ekranowej (zwaną też IME) i sterować nią w podobny sposób, podobnie jak w przypadku interakcji z paskami systemowymi. Aplikacja może też używać WindowInsetsAnimationCompat, aby płynnie przechodzić między klawiaturami po otwarciu lub zamknięciu klawiatury programowej.

Rysunek 1. Dwa przykłady otwartego przejścia z klawiatury programowej.

Wymagania wstępne

Zanim skonfigurujesz elementy sterujące i animacje na klawiaturze programowej, skonfiguruj aplikację tak, by wyświetlała od krawędzi do krawędzi. Dzięki temu może on obsługiwać ustawienia okien systemowych, takie jak paski systemowe i klawiatura ekranowa.

Sprawdź widoczność oprogramowania klawiatury

Użyj WindowInsets, aby sprawdzić widoczność klawiatury programowej.

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;

Możesz też użyć narzędzia ViewCompat.setOnApplyWindowInsetsListener, aby obserwować zmiany w widoczności klawiatury programowej.

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

Synchronizuj animacje za pomocą klawiatury programowej

Użytkownik klikający pole wprowadzania tekstu powoduje wysunięcie klawiatury z dołu ekranu na miejsce, jak pokazano w tym przykładzie:

Rysunek 2. Zsynchronizowana animacja klawiatury.
  • Przykład oznaczony jako „Niezsynchronizowane” na ilustracji 2 pokazuje domyślne zachowanie w Androidzie 10 (poziom interfejsu API 29), w którym pole tekstowe i zawartość aplikacji wsuwają się na miejsce, zamiast synchronizować się z animacją klawiatury. Działanie, które może wprawiać w dezorientację.

  • Na Androidzie 11 (poziom interfejsu API 30) i nowszych możesz używać WindowInsetsAnimationCompat do synchronizowania przejścia w aplikacji, gdy klawiatura przesuwająca się w górę i w dół od dołu ekranu. Wygląda to płynniej, jak widać w przykładzie oznaczonym jako „Zsynchronizowane” na rys. 2.

Skonfiguruj widok danych WindowInsetsAnimationCompat.Callback tak, aby był synchronizowany z animacją klawiatury.

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

Istnieje kilka metod zastąpienia w WindowInsetsAnimationCompat.Callback: onPrepare(), onStart(), onProgress() i onEnd(). Zacznij od wywołania onPrepare(), zanim cokolwiek zmieni się układ.

Funkcja onPrepare jest wywoływana, gdy rozpoczyna się animacja z wgłębieniami i przed ponownym nałożeniem widoków z powodu animacji. Możesz jej użyć do zapisania stanu początkowego, który w tym przypadku jest dolną współrzędną widoku.

Obraz pokazujący dolną współrzędną stanu początkowego na poziomie głównym.
Rysunek 3. Rejestrowanie stanu rozpoczęcia przy użyciu onPrepare().

Ten fragment kodu pokazuje przykładowe wywołanie funkcji 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();
}

Funkcja onStart jest wywoływana, gdy rozpoczyna się animacja wstawienia. Możesz go użyć, by we wszystkich właściwościach widoku przejść do stanu końcowego po zmianie układu. Jeśli masz wywołanie zwrotne OnApplyWindowInsetsListener ustawione na dowolny z widoków, jest ono już w tym momencie wywoływane. To dobry moment na zapisanie stanu końcowego właściwości widoku.

Obraz przedstawiający dolną współrzędną stanu końcowego widoku
Rysunek 4. Używanie onStart() do rejestrowania stanu końcowego.

Ten fragment kodu pokazuje przykładowe wywołanie funkcji 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;
}

Funkcja onProgress jest wywoływana, gdy wstawki zmieniają się w trakcie odtwarzania animacji, więc można ją zastąpić i otrzymywać powiadomienia o każdej klatce podczas animacji klawiatury. Zaktualizuj właściwości widoku, aby jego animacja była synchronizowana z klawiaturą.

Na tym etapie wszystkie zmiany układu zostały zakończone. Jeśli np. użyjesz funkcji View.translationY, aby przesunąć widok, wartość ta będzie stopniowo zmniejszać się przy każdym wywołaniu tej metody, aż do osiągnięcia pozycji 0 względem pierwotnego układu.

Rysunek 5. Do synchronizowania animacji służy onProgress().

Ten fragment kodu pokazuje przykładowe wywołanie funkcji 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;
}

Opcjonalnie możesz zastąpić ustawienie onEnd. Metoda ta jest wywoływana po zakończeniu animacji. To dobry moment na wyczyszczenie wszelkich tymczasowych zmian.

Dodatkowe materiały