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.
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:
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. });
W WindowInsetsAnimationCompat.Callback można zastąpić kilka metod, a mianowicie: onPrepare(), onStart(), onProgress() i 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.
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.
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.
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
- WindowInsetsAnimation w GitHubie.