Control and animate the software keyboard

Using WindowInsetsCompat, your app can query and control the on-screen keyboard (also called the IME) in a similar way to the system bars. Also, your app can use WindowInsetsAnimationCompat to create seamless transitions when the software keyboard is opened or closed.

Prerequisites

Before setting up control and animation for the software keyboard, configure your app to display edge-to-edge. This allows it to handle system window insets such as the system bars and the on-screen keyboard.

Check keyboard software visibility

Use WindowInsets to check the software keyboard visibility.

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;

Alternatively, you can use ViewCompat.setOnApplyWindowInsetsListener to observe changes to software keyboard visibility.

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

Synchronize animation with the software keyboard

A user tapping a text input field causes the keyboard to slide into place from the bottom of the screen, as shown in the following examples.

Figure 1: Synchronized keyboard animation
  • The first of the preceding examples shows the default behavior in Android 10 (API level 29), in which the text field and content of your app snap into place instead of synchronizing with the keyboard's animation -- behavior that can be visually jarring.

  • In Android 11 (API level 30) and higher, you can use WindowInsetsAnimationCompat to synchronize the transition of the app with the keyboard sliding up and down from the bottom of the screen. This looks much smoother, as shown in the second of the preceding examples.

  1. Configure WindowInsetsAnimationCompat.Callback with the view to be synchronized with the keyboard animation.

    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…
        });
    

    There are several methods to override in WindowInsetsAnimationCompat.Callback, namely onPrepare(), onStart(), onProgress(), and onEnd(). Let's start with calling onPrepare() before any of the layout changes.

  2. Use onPrepare to save the start state, which in this case is the bottom coordinate of the view.

    Figure 2: Using onPrepare() to record the start state

    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();
    }
    
  3. Call onStart to set all the view properties to the end state of the layout changes. If you have an OnApplyWindowInsetsListener callback set to any of the views, it has already been called at this point. This is a good time to save the end state of the view properties.

    Figure 3: Using onStart() to record the start state

    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;
    }
    
  4. Override onProgress, which is called every frame during the keyboard animation. Update the view properties so that the view animates in synchronization with the keyboard.

    Note all the layout changes have completed at this point. For example, if you use View.translationY to shift the view, the value should gradually decrease for every call of this method and eventually reach 0 to the original layout position.

    Figure 4: Using onProgress() to synchronize the animations

    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;
    }
    

Optionally, you can override onEnd. This method is called after the animation is over, so it is a good time to clean up any temporary changes.

More resources