Управление и анимация программной клавиатуры

Попробуйте способ создания композиций.
Jetpack Compose — это рекомендуемый набор инструментов для создания пользовательского интерфейса для Android. Узнайте, как работать с клавиатурой в Compose.

Используя WindowInsetsCompat , ваше приложение может запрашивать и управлять экранной клавиатурой (также называемой IME ) аналогично тому, как оно взаимодействует с системными панелями. Ваше приложение также может использовать WindowInsetsAnimationCompat для создания плавных переходов при открытии или закрытии программной клавиатуры.

Рисунок 1. Два примера перехода от открытого к закрытому состоянию программной клавиатуры.

Предварительные требования

Прежде чем настраивать управление и анимацию для программной клавиатуры, сконфигурируйте приложение для отображения текста от края до края . Это позволит ему обрабатывать отступы системных окон, такие как системные панели и экранная клавиатура.

Проверить видимость программного обеспечения клавиатуры

Используйте WindowInsets для проверки видимости программной клавиатуры.

Котлин

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 для отслеживания изменений видимости программной клавиатуры.

Котлин

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. Синхронизированная анимация клавиатуры.
  • Пример, обозначенный как «Несинхронизировано» на рисунке 2, демонстрирует поведение по умолчанию в Android 10 (уровень API 29), при котором текстовое поле и содержимое приложения автоматически подстраиваются под анимацию клавиатуры, вместо того чтобы синхронизироваться с ней — такое поведение может выглядеть неестественно.

  • В Android 11 (уровень API 30) и выше можно использовать WindowInsetsAnimationCompat для синхронизации перехода приложения с движением клавиатуры вверх и вниз от нижнего края экрана. Это выглядит более плавно, как показано в примере с пометкой «Синхронизировано» на рисунке 2.

Настройте WindowInsetsAnimationCompat.Callback , указав представление, которое необходимо синхронизировать с анимацией клавиатуры.

Котлин

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 вызывается при начале анимации отступов и перед перекомпоновкой элементов представления из-за анимации. Его можно использовать для сохранения начального состояния, которым в данном случае является нижняя координата элемента представления.

Изображение, показывающее начальную нижнюю координату корневого представления.
Рисунок 3. Использование onPrepare() для записи начального состояния.

Следующий фрагмент кода демонстрирует пример вызова функции onPrepare :

Котлин

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 для какого-либо из представлений, он уже вызывается в этот момент. Это подходящий момент для сохранения конечного состояния свойств представления.

Изображение, показывающее конечные координаты нижней части вида.
Рисунок 4. Использование функции onStart() для записи конечного состояния.

Следующий фрагмент кода демонстрирует пример вызова функции onStart :

Котлин

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 , возвращаясь к исходному положению в компоновке.

Рисунок 5. Использование onProgress() для синхронизации анимаций.

Следующий фрагмент кода демонстрирует пример вызова функции onProgress :

Котлин

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 . Этот метод вызывается после завершения анимации. Это подходящий момент для очистки любых временных изменений.

Дополнительные ресурсы