Steruj klawiaturą programową i korzystaj z animacji

Za pomocą WindowInsetsCompat aplikacja może wysyłać zapytania i sterować klawiaturą ekranową (znaną też jako IME) podobnie jak obsługuje ona paski systemowe. Aplikacja może również używać WindowInsetsAnimationCompat do tworzenia płynnych przejść, gdy klawiatura programowa jest otwarta lub zamknięta.

Rysunek 1. 2 przykłady przejścia otwartego i otwartego zamknięcia klawiatury programowej.

Wymagania wstępne

Zanim ustawisz elementy sterujące i animacje dla klawiatury oprogramowania, skonfiguruj aplikację tak, aby wyświetlała się od krawędzi do krawędzi. Dzięki temu obsługuje systemowe wstawki w oknach, takie jak paski systemowe i klawiatura ekranowa.

Sprawdź widoczność oprogramowania klawiatury

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

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ć polecenia 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 animację z klawiaturą programową

Gdy użytkownik klika pole do wprowadzania tekstu, klawiatura przesuwa się z dołu ekranu w to miejsce, jak widać w tym przykładzie:

Rysunek 2. Animacja zsynchronizowanej klawiatury.
  • Przykład z etykietą „Niezsynchronizowana” na ilustracji 2 pokazuje domyślne zachowanie w Androidzie 10 (poziom interfejsu API 29), w którym pole tekstowe i zawartość aplikacji wpadają na swoje miejsce, zamiast synchronizować się z animacją klawiatury, co może przykuwać wzrok.

  • W Androidzie 11 (poziom interfejsu API 30) i nowszych możesz używać WindowInsetsAnimationCompat do synchronizowania przejścia w aplikacji za pomocą klawiatury przesuwającej w górę lub w dół z dołu ekranu. Wygląda to płynniej, jak widać w przykładzie z etykietą „Zsynchronizowane” na rysunku 2.

Skonfiguruj WindowInsetsAnimationCompat.Callback tak, aby widok 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.
    });

Jest kilka metod zastępowania w WindowInsetsAnimationCompat.Callback, m.in. onPrepare(), onStart(), onProgress() i onEnd(). Zacznij od wywołania onPrepare() przed zmianą układu.

Funkcja onPrepare jest wywoływana, gdy rozpoczyna się animacja wcięcia i przed ponownym umieszczeniem widoków ze względu na animację. Możesz jej użyć do zapisania stanu początkowego, który w tym przypadku jest dolną współrzędną widoku.

Obraz przedstawiający dolną współrzędną stanu początkowego widoku głównego.
Rysunek 3. Użycie onPrepare() do rejestrowania stanu początkowego.

Ten fragment kodu zawiera 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 wcięcia. Możesz go użyć, by we wszystkich właściwościach widoku ustawić stan końcowy po zmianach układu. Jeśli masz wywołanie zwrotne OnApplyWindowInsetsListener ustawione dla dowolnego z widoków, jest ono już wywoływane w tym punkcie. To dobry moment na zapisanie końcowego stanu właściwości widoku.

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

Ten fragment kodu zawiera 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 ramach animacji, więc można ją zastąpić i otrzymywać powiadomienie przy każdej klatce animacji. Zaktualizuj właściwości widoku, aby animowany widok był zsynchronizowany z klawiaturą.

Na tym etapie wszystkie zmiany układu są zakończone. Jeśli np. używasz parametru View.translationY do przesunięcia widoku, wartość stopniowo maleje przy każdym wywołaniu tej metody, aż w końcu osiągnie pozycję 0 w pozycji układu pierwotnego.

Rysunek 5. Do synchronizowania animacji używana jest onProgress().

Ten fragment kodu zawiera 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;
}

Możesz też zastąpić zasadę onEnd. Metoda ta jest wywoływana po zakończeniu animacji. To dobry moment na usunięcie wszelkich tymczasowych zmian.

Dodatkowe materiały