Steruj klawiaturą programową i korzystaj z animacji

Wypróbuj tworzenie wiadomości
Jetpack Compose to zalecany zestaw narzędzi interfejsu na Androida. Dowiedz się, jak korzystać z klawiatury w Compose.

Za pomocą WindowInsetsCompat Twoja aplikacja może wysyłać zapytania do klawiatury ekranowej (zwanej też IME) i sterować nią w sposób podobny do interakcji z paskami systemowymi. Aplikacja może też używać WindowInsetsAnimationCompat, aby zapewnić płynne przejścia podczas otwierania i zamykania klawiatury.

Rysunek 1. Dwa przykłady przejścia od otwartej do zamkniętej klawiatury

Wymagania wstępne

Zanim skonfigurujesz sterowanie i animację klawiatury programowej, skonfiguruj aplikację tak, aby wypełniała cały ekran. Dzięki temu może obsługiwać wstawki okna systemowego, takie jak paski systemowe i klawiatura ekranowa.

Sprawdzanie widoczności klawiatury programowej

Aby sprawdzić widoczność klawiatury ekranowej, użyj skrótu WindowInsets.

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ć ViewCompat.setOnApplyWindowInsetsListener, aby obserwować zmiany w widoczności klawiatury.

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

Synchronizacja animacji z klawiaturą ekranową

Gdy użytkownik kliknie pole tekstowe, klawiatura przesunie się z dołu ekranu, jak pokazano w tym przykładzie:

Rysunek 2. Zsynchronizowana animacja klawiatury.
  • Przykład oznaczony etykietą „Niezsynchronizowany” na rysunku 2 przedstawia domyślne działanie Androida 10 (poziom interfejsu API 29), w którym pole tekstowe i treść aplikacji są umieszczane na miejscu zamiast synchronizowania się z animowanym obrazem klawiatury. Takie zachowanie może być nieprzyjemne dla oka.

  • W Androidzie 11 (poziom interfejsu API 30) i nowszych możesz użyć WindowInsetsAnimationCompat, aby zsynchronizować przejście aplikacji z klawiaturą przesuwaną w górę i w dół od dołu ekranu. Wygląda to bardziej płynnie, jak widać na rysunku 2 w przykładzie oznaczonym etykietą „Synchronizacja”.

Skonfiguruj widok WindowInsetsAnimationCompat.Callback, aby był zsynchronizowany 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, a mianowicie: onPrepare(), onStart(), onProgress()onEnd(). Zacznij od wywołania funkcji onPrepare(), zanim wprowadzisz jakiekolwiek zmiany układu.

onPrepare jest wywoływany, gdy rozpoczyna się animacja wstawionych elementów, i zanim widoki zostaną ponownie rozmieszczone z powodu animacji. Możesz go użyć, aby zapisać stan początkowy, którym w tym przypadku jest dolna współrzędna widoku.

Ilustracja pokazująca dolną współrzędną stanu początkowego widoku katalogu.
Rysunek 3. Użycie funkcji onPrepare() do zarejestrowania stanu początkowego.

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 wstawionych elementów. Możesz użyć go do ustawienia wszystkich właściwości widoku na stan końcowy zmian układu. Jeśli masz wywołanie zwrotne OnApplyWindowInsetsListener skonfigurowane w przypadku któregoś z widoków, jest ono już wywołane. To dobry moment na zapisanie końcowego stanu właściwości widoku.

Ilustracja pokazująca dolną współrzędną widoku w stanie końcowym
Rysunek 4. Użyj onStart(), aby zarejestrować stan końcowy.

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 w ramach animacji zmieniają się wstawki. Możesz ją zastąpić i być powiadamiany o każdej klatce podczas animacji klawiatury. Zaktualizuj właściwości widoku, aby animacja była synchronizowana z klawiaturą.

W tej chwili wszystkie zmiany układu zostały wprowadzone. Jeśli np. użyjesz View.translationY, aby zmienić widok, wartość będzie stopniowo maleć przy każdym wywołaniu tej metody i ostatecznie osiągnie 0, czyli pierwotną pozycję układu.

Rysunek 5. Użyj onProgress() do synchronizowania animacji.

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ć wartość onEnd. Ta metoda jest wywoływana po zakończeniu animacji. To dobry moment na usunięcie tymczasowych zmian.

Dodatkowe materiały