Steruj klawiaturą programową i korzystaj z animacji

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

Za pomocą WindowInsetsCompat aplikacja może wysyłać zapytania do klawiatury ekranowej (zwanej też edytorem IME) i sterować nią w podobny sposób jak w przypadku pasków systemu. Aplikacja może też używać WindowInsetsAnimationCompat do tworzenia płynnych przejść, gdy klawiatura programowa jest otwierana lub zamykana.

Rysunek 1. Dwa przykłady przejścia otwierania i zamykania klawiatury ekranowej.

Wymagania wstępne

Zanim skonfigurujesz sterowanie i animację klawiatury ekranowej, skonfiguruj aplikację tak, aby wyświetlała się od krawędzi do krawędzi. Dzięki temu może obsługiwać wstawki okien systemowych, takie jak paski systemowe i klawiatura ekranowa.

Domyślna animacja zsynchronizowanych wcięć

Od poziomu interfejsu API 37.2 i wyższego aplikacje, które korzystają z domyślnych korekt układu okna (np. SOFT_INPUT_ADJUST_RESIZE lub SOFT_INPUT_ADJUST_PAN podczas wyświetlania IME za pomocą WindowInsetsController.show(WindowInsets.Type.ime())) bez implementowania niestandardowego WindowInsetsAnimationCompat.Callback, mogą korzystać z synchronizowanych na poziomie systemu animacji klatka po klatce. Gdy ta funkcja jest włączona, automatycznie stosuje wcięcia w każdej ramce i wywołuje przejście układu, co zapewnia płynne przejście podczas przesuwania klawiatury.

Aby włączyć to działanie, dodaj ten tag <property> do aktywności lub aplikacji w pliku AndroidManifest.xml:

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

Wykonywanie animacji wstawek poza głównym wątkiem

Domyślnie rejestracja elementu WindowInsetsAnimationCompat.Callback wymusza uruchamianie animacji wstawki okna w głównym wątku aplikacji. Jest to konieczne, jeśli aplikacja stale aktualizuje i synchronizuje układ widoku podczas animacji (np. w metodzie onProgress). Jeśli jednak aplikacja musi tylko nasłuchiwać przejść cyklu życia (np. gdy IME zaczyna lub kończy animację) i nie musi aktualizować widoków klatka po klatce, uruchamianie jej w wątku głównym może powodować niepotrzebne obciążenie i zacinanie się animacji, zwłaszcza jeśli wątek główny jest zajęty innymi zadaniami.

Od poziomu interfejsu API 37.2 i wyższego możesz zamiast tego używać ViewTreeObserver.WindowInsetsAnimationListener do obserwowania tych przejść. Ponieważ ten odbiornik nie otrzymuje aktualizacji postępu klatka po klatce (brak wywołania zwrotnego onProgress), system może uruchamiać animację wstawki w osobnym wątku animacji, a nie w wątku głównym, co zapewnia płynniejsze przejście.

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

Sprawdzanie widoczności 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ć ViewCompat.setOnApplyWindowInsetsListener do obserwowania zmian widoczności klawiatury ekranowej.

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

Synchronizowanie animacji z klawiaturą ekranową

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

Rysunek 2. Zsynchronizowana animacja klawiatury.
  • Przykład oznaczony jako „Niesynchronizowany” na rysunku 2 pokazuje domyślne działanie w Androidzie 10 (poziom API 29), w którym pole tekstowe i treść aplikacji zatrzaskują się na miejscu zamiast synchronizować się z animacją klawiatury, co może być wizualnie nieprzyjemne.

  • W Androidzie 11 (poziom interfejsu API 30) i nowszym możesz użyć WindowInsetsAnimationCompat, aby zsynchronizować przejście aplikacji z wysuwaniem i chowanie klawiatury u dołu ekranu. Wygląda to płynniej, jak widać na przykładzie oznaczonym jako „Zsynchronizowane” na rysunku 2.

.

Skonfiguruj WindowInsetsAnimationCompat.Callback widok, który ma 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.
    });

WindowInsetsAnimationCompat.Callback można zastąpić kilka metod, a mianowicie: onPrepare(), onStart(), onProgress()onEnd(). Zacznij od wywołania onPrepare() przed wprowadzeniem jakichkolwiek zmian w układzie.

onPrepare jest wywoływana, gdy rozpoczyna się animacja wstawień i zanim widoki zostaną ponownie rozmieszczone z powodu animacji. Możesz go użyć do zapisania stanu początkowego, którym w tym przypadku jest dolny współrzędny widoku.

Ilustracja pokazująca współrzędną dolną stanu początkowego widoku głównego.
Rysunek 3. Użyj aplikacji onPrepare(), aby zarejestrować stan początkowy.

Poniższy 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();
}

onStart jest wywoływana, gdy rozpoczyna się animacja wstawek. Możesz go użyć, aby ustawić wszystkie właściwości widoku na stan końcowy zmian układu. Jeśli w którymś z widoków masz ustawione wywołanie zwrotne OnApplyWindowInsetsListener, zostanie ono wywołane w tym momencie. To dobry moment, aby zapisać stan końcowy właściwości widoku.

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

Poniższy 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;
}

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

Wszystkie zmiany układu zostały już wprowadzone. Jeśli na przykład użyjesz View.translationY, aby przesunąć widok, wartość będzie stopniowo zmniejszać się przy każdym wywołaniu tej metody i ostatecznie osiągnie 0, co oznacza powrót do pierwotnego układu.

Rysunek 5. Użyj onProgress(), aby zsynchronizować animacje.

Poniższy 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ć onEnd. Ta metoda jest wywoływana po zakończeniu animacji. To dobry moment, aby usunąć wszelkie tymczasowe zmiany.

Dodatkowe materiały