Za pomocą WindowInsetsCompat aplikacja może wysyłać zapytania do klawiatury ekranowej (nazywanej też IME) i ją kontrolować, podobnie jak w przypadku interakcji z paskami systemu. Aplikacja może też używać WindowInsetsAnimationCompat, aby tworzyć płynne przejścia podczas otwierania i zamykania klawiatury ekranowej.
Wymagania wstępne
Zanim skonfigurujesz sterowanie klawiaturą ekranową i jej animację, skonfiguruj aplikację tak, aby wyświetlała się od krawędzi do krawędzi. Dzięki temu może obsługiwać wstawki okna systemowego, takie jak paski systemowe i klawiatura ekranowa.
Domyślna animacja zsynchronizowanych wstawek
Od poziomu interfejsu API 37.2 aplikacje, które używają domyślnych dostosowań układu okna (takich jak SOFT_INPUT_ADJUST_RESIZE lub SOFT_INPUT_ADJUST_PAN podczas wyświetlania edytora IME za pomocą WindowInsetsController.show(WindowInsets.Type.ime())) bez implementacji niestandardowego WindowInsetsAnimationCompat.Callback, mogą korzystać ze zsynchronizowanych animacji klatka po klatce na poziomie systemu. Gdy ta funkcja jest włączona, automatycznie stosuje wstawki w każdej klatce i wywołuje przejście układu, co zapewnia płynne przejście podczas ruchu klawiatury.
Aby włączyć to zachowanie, dodaj do aktywności lub aplikacji w pliku AndroidManifest.xml ten tag <property>:
<property
android:name="android.window.PROPERTY_COMPAT_ALLOW_SYNCHRONIZED_INSETS_ANIMATION"
android:value="true" />
Wykonywanie animacji wstawionych elementów poza głównym wątkiem
Domyślnie zarejestrowanie 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 rozpoczyna lub kończy animację) i nie musi aktualizować widoków klatka po klatce, uruchamianie w głównym wątku może powodować niepotrzebne obciążenie i zacinanie się animacji, zwłaszcza jeśli główny wątek jest zajęty.
Od poziomu interfejsu API 37.2 możesz używać ViewTreeObserver.WindowInsetsAnimationListener, aby obserwować te przejścia. Ponieważ ten detektor nie otrzymuje aktualizacji postępu klatka po klatce (brak wywołania zwrotnego onProgress), system może uruchomić animację wstawki w dedykowanym wątku animacji, a nie w 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 ekranowej.
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 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 danych, klawiatura wysunie się z dołu ekranu, jak w tym przykładzie:
Przykład oznaczony jako „Niezsynchronizowany” na rysunku 2 pokazuje działanie domyślne w Androidzie 10 (poziom interfejsu API 29), w którym pole tekstowe i zawartość aplikacji są przyciągane do miejsca, zamiast synchronizować się z animacją klawiatury. Może to wyglądać nienaturalnie.
W Androidzie 11 (poziom interfejsu API 30) i nowszych wersjach możesz używać
WindowInsetsAnimationCompat, aby synchronizować przejście aplikacji z klawiaturą wysuwaną z dołu ekranu. Dzięki temu obraz jest płynniejszy, jak widać na przykładzie oznaczonym jako „Synchronized” na rysunku 2.
Skonfiguruj WindowInsetsAnimationCompat.Callback z widokiem, 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. });
W klasie WindowInsetsAnimationCompat.Callback jest kilka metod do zastąpienia, a mianowicie:
onPrepare(),
onStart(),
onProgress()
i onEnd().
Zacznij od wywołania onPrepare() przed wprowadzeniem jakichkolwiek zmian w układzie.
Metoda onPrepare jest wywoływana, gdy rozpoczyna się animacja wstawek 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 dolna współrzędna widoku.
onPrepare() do rejestrowania stanu początkowego.
Poniższy fragment kodu pokazuje przykładowe wywołanie metody 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(); }
Metoda onStart jest wywoływana po rozpoczęciu animacji wstawek. Możesz go użyć, aby ustawić wszystkie właściwości widoku na stan końcowy zmian układu. Jeśli masz wywołanie zwrotne OnApplyWindowInsetsListener ustawione na którykolwiek z widoków, jest ono już wywoływane w tym momencie. To dobry moment na zapisanie stanu końcowego właściwości widoku.
onStart() do rejestrowania stanu końcowego.
Poniższy fragment kodu pokazuje przykładowe wywołanie 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; }
Metoda onProgress jest wywoływana, gdy wstawki zmieniają się w ramach animacji, więc możesz ją zastąpić i otrzymywać powiadomienia o 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 wprowadzone. Jeśli na przykład użyjesz metody View.translationY do przesunięcia widoku, wartość będzie stopniowo maleć przy każdym wywołaniu tej metody i ostatecznie osiągnie wartość 0 w pierwotnej pozycji układu.
onProgress() do synchronizowania animacji.
Poniższy fragment kodu pokazuje przykładowe wywołanie metody 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 na usunięcie wszelkich tymczasowych zmian.
Dodatkowe materiały
- WindowInsetsAnimation na GitHubie.