С помощью WindowInsetsCompat приложение может запрашивать информацию об экранной клавиатуре (также называемой IME) и управлять ею так же, как и системными строками. Приложение также может использовать WindowInsetsAnimationCompat, чтобы обеспечить плавные переходы при открытии или закрытии виртуальной клавиатуры.
Требования
Прежде чем настраивать управление виртуальной клавиатурой и ее анимацию, включите в приложении полноэкранный режим. Это позволяет обрабатывать вставки системных окон, например системные строки и экранную клавиатуру.
Анимация синхронизированных вставок по умолчанию
Начиная с уровня API 37.2 приложения, которые используют стандартные настройки макета окна (например, SOFT_INPUT_ADJUST_RESIZE или SOFT_INPUT_ADJUST_PAN при показе IME с помощью WindowInsetsController.show(WindowInsets.Type.ime())) без реализации пользовательского WindowInsetsAnimationCompat.Callback, могут использовать синхронизированную покадровую анимацию на уровне системы. Когда эта функция включена, она автоматически применяет вставки к каждому кадру и запускает проход макета, создавая плавный переход при перемещении клавиатуры.
Чтобы включить эту функцию, добавьте в файл AndroidManifest.xml следующий тег <property>:
<property
android:name="android.window.PROPERTY_COMPAT_ALLOW_SYNCHRONIZED_INSETS_ANIMATION"
android:value="true" />
Выполняйте анимацию вставок вне основного потока
По умолчанию регистрация WindowInsetsAnimationCompat.Callback обеспечивает выполнение анимации вставки окна в основном потоке приложения. Это необходимо, если ваше приложение постоянно обновляет и синхронизирует макет представления во время анимации (например, в методе onProgress). Однако если вашему приложению нужно только прослушивать переходы жизненного цикла (например, когда IME запускает или завершает анимацию) и не нужно обновлять представления кадр за кадром, запуск в основном потоке может вызвать ненужные накладные расходы и заикание анимации, особенно если основной поток занят.
Начиная с уровня API 37.2 и выше, для отслеживания этих переходов можно использовать ViewTreeObserver.WindowInsetsAnimationListener. Поскольку этот прослушиватель не получает покадровые обновления прогресса (нет обратного вызова onProgress), система может запустить анимацию вставки в выделенном потоке анимации, а не в основном потоке, что приведет к более плавному переходу.
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);
Проверьте видимость ПО клавиатуры
Используйте 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;
Вы также можете использовать ViewCompat.setOnApplyWindowInsetsListener, чтобы отслеживать изменения видимости виртуальной клавиатуры.
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; });
Синхронизация анимации с виртуальной клавиатурой
Когда пользователь нажимает на поле ввода текста, клавиатура выдвигается снизу, как показано на рисунке ниже.
Пример с пометкой "Несинхронизированный" на рисунке 2 показывает режим работы по умолчанию в Android 10 (уровень API 29), когда текстовое поле и контент приложения встают на место, а не синхронизируются с анимацией клавиатуры. Это может выглядеть не очень хорошо.
В Android 11 (уровень API 30) и более поздних версиях можно использовать
WindowInsetsAnimationCompat, чтобы синхронизировать переход приложения с появлением и скрытием клавиатуры в нижней части экрана. Это выглядит более плавно, как показано в примере с пометкой "Синхронизировано" на рисунке 2.
Настройте WindowInsetsAnimationCompat.Callback с представлением, которое нужно синхронизировать с анимацией клавиатуры.
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 есть несколько методов, которые можно переопределить:
onPrepare(),
onStart(),
onProgress() и
onEnd().
Сначала вызовите onPrepare(), а затем внесите изменения в макет.
onPrepare вызывается, когда начинается анимация вставок и до того, как макеты представлений будут перестроены из-за анимации. Вы можете использовать его, чтобы сохранить начальное состояние,
которое в данном случае является нижней координатой представления.
onPrepare() для записи начального состояния.
Ниже приведен пример вызова 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 вызывается при запуске анимации вставок. С его помощью можно задать для всех свойств представления конечное состояние изменений макета. Если для какого-либо из представлений задан обратный вызов OnApplyWindowInsetsListener, он уже будет выполнен. Это подходящий момент, чтобы сохранить конечное состояние свойств представления.
onStart(), чтобы записать конечное состояние.
Ниже приведен пример вызова 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 вызывается, когда вставки меняются в рамках анимации. Вы можете переопределить этот метод и получать уведомления о каждом кадре во время анимации клавиатуры. Обновите свойства представления, чтобы оно анимировалось синхронно с клавиатурой.
Все изменения макета завершены. Например, если вы используете View.translationY для сдвига представления, значение постепенно уменьшается при каждом вызове этого метода и в конечном итоге достигает 0 исходной позиции макета.
onProgress(), чтобы синхронизировать анимации.
Ниже приведен пример вызова 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; }
При необходимости вы можете переопределить onEnd. Этот метод вызывается после завершения анимации. Это хороший момент, чтобы убрать все временные изменения.
Дополнительные ресурсы
- WindowInsetsAnimation на GitHub.