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

Попробуйте способ «Композиции»
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

Ява

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
}

Ява

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

Ява

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

Ява

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
}

Ява

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
}

Ява

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

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