Window Insets and Keyboard Animations Tutorial for Android 11

In this tutorial, you’ll learn about Window Insets and Keyboard Animations in Android 11 and how to add these features to your android app. By Carlos Mota.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Preparing Your App for Keyboard Animations

To animate your keyboard, or IME, and the surrounding UI, you need to set your app as fullscreen because the IME is part of the system UI. Prior to Android 11, you could do this via:

window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
  View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
  View.SYSTEM_UI_FLAG_FULLSCREEN

However, this API is now deprecated. That’s a good thing, since it was always tricky to find the right combination of flags. In it’s place, Android backported a single method to the previous Android versions via WindowCompat that lets you achieve the same behavior.

Go to MainActivity.kt, and before calling super.onCreate(savedInstanceState), add:

WindowCompat.setDecorFitsSystemWindows(window, !isAtLeastAndroid11())

Android studio prompts you for two imports. So, import androidx.core.view.WindowCompat and com.raywenderlich.android.braindump.isAtLeastAndroid11.

Here, the second parameter, !isAtLeastAndroid11(), defines whether the app will handle the system windows. If the device runs Android 11 or newer, this value will be false so the app can define the keyboard animations. On lower versions, since these functionalities aren’t available, the value will be true so the system controls them.

To understand these differences, compile and run the on a device with a version lower than Android 11.

Brain dump app running on Android 10 where the app doesn't handle the system bars

Everything seems perfect! What if you run it on a device with Android 11?

Brain dump app running on Android 11 where the app handles the system bars

As you can see, the UI now overlaps the system bars. This overlap happens because your app asked to occupy the entire screen. At the same time, it said it would take care of the system windows, which it didn’t.

So, time to do that. :]

Handling System Windows

setDecorFitsSystemWindows is only set for Android versions 11 or higher. So, open RWCompat11.kt and update setUiWindowInsets :

//1
private var posTop = 0
private var posBottom = 0
 
fun setUiWindowInsets() {
  //2
  ViewCompat.setOnApplyWindowInsetsListener(container) { _, insets ->
    //3
    if (posBottom == 0) {
      posTop = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top
      posBottom = insets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom
    }
    //4
    container.updateLayoutParams<ViewGroup.MarginLayoutParams> {
      updateMargins(
        top = posTop,
        bottom = posBottom)
    }
 
    insets
 }
}

When prompted for imports, use the following:

import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updateLayoutParams
import androidx.core.view.updateMargins

setUiWindowInsets is already declared, so you need to add its content.

Here’s a step-by-step breakdown of this logic:

  1. You declare these two fields globally because you’ll use them later in a method that handles the keyboard animations.
  2. To guarantee compatibility with previous Android versions, you give precedence to the appcompat APIs.
  3. On the first run, both fields are empty, so they need to be set. Since the input bar should be on top of the navigation UI and the toolbar should be under the status bar, you’ll need to check the margins of these two insets and update the container margins accordingly.
  4. The container received corresponds to the activity’s root view, based on the values defined earlier. You use them to update the view bottom and top margin. With this, no component is overlaid.
Note: You need to calculate postTop and postBottom inside the setOnApplyWindowInsetsListener. Otherwise when you’re querying systemBars insets you might receive 0 as top and bottom margins. There’s no guarantee the views will be ready outside this listener.

Now that you rearranged the UI to be within the screen limits, hit compile and run the app.

Brain dump app running on Android 11 with the UI adapted to the system windows

Everything fits perfectly – well done! :]

Note: Want to know more about window insets and gesture navigation? Check out this Gesture Navigation Tutorial for Android.

Now that the UI fits its window, it’s time to animate the keyboard.

Animating the Keyboard

You’ll use the WindowInsetsAnimationCallback to animate the keyboard.

This API is only available on Android 11 and higher. So in RWCompat11.kt, update animateKeyboardDisplay as follows:

//1
@RequiresApi(Build.VERSION_CODES.R)
fun animateKeyboardDisplay() {
  //2
  val cb = object : WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
    //3
    override fun onProgress(insets: WindowInsets, animations: MutableList<WindowInsetsAnimation>): WindowInsets {
      //4
      posBottom = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom +
        insets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom
      //5
      container.updateLayoutParams<ViewGroup.MarginLayoutParams> {
        updateMargins(
          top = posTop,
          bottom = posBottom)
      }
 
      return insets
    }
  }
  //6
  container.setWindowInsetsAnimationCallback(cb)
}

Here’s a logic breakdown:

To recalculate this margin, you need to get the systemBars bottom margin and add it to the current size of the IME. Otherwise, your UI will be under the system navigation bar or the keyboard.

If the user opens the keyboard, this sum will increase until the animation finishes. If the user closes the keyboard, the sum will decrease until the final result is the value of the systemBars bottom.

Position in the screen of the system bars and the IME

  1. setWindowInsetsAnimationCallback is only available on Android R. Although RWCompat11.kt only contains code that targets this API, it’s good practice to add this annotation to notify other programmers they need to check if the device supports this call.
  2. You can use two modes here: DISPATCH_MODE_CONTINUE_ON_SUBTREE and DISPATCH_MODE_STOP. In this scenario, you used the latter since the animation occurs at the parent level. And also there’s no need to propagate this event into other levels of the view hierarchy.
  3. In this use case, you used onProgress to update the UI. There are a few methods available that can be useful in other scenarios. More on this shortly.
  4. Every time there’s a change on WindowInsetsCompat, onProgress is called and you need to update the root view margins. This guarantees the UI updates seamlessly with the animation.

    To recalculate this margin, you need to get the systemBars bottom margin and add it to the current size of the IME. Otherwise, your UI will be under the system navigation bar or the keyboard.

    If the user opens the keyboard, this sum will increase until the animation finishes. If the user closes the keyboard, the sum will decrease until the final result is the value of the systemBars bottom.

    Position in the screen of the system bars and the IME

  5. With these new values you update the margins, so the UI will synchronize with the keyboard animation.
  6. After defining the callback it’s important to set it. Otherwise, nothing will happen.

With everything defined, play a bit with these new animations. Compile and run the app. See how smoothly the keyboard opens and closes.

Opening and closing the keyboard with the newly added animations

WindowInsets Animation Lifecycle

The other methods available in setWindowInsetsAnimationCallback are:

  • onPrepare: Lets you record any specific configuration before the animation takes place. For instance, you can record the initial coordinates of a view.
  • onStart: Similar to the previous method, you can use it to save any value that’s going to change later. You can also use it to trigger any other behavior related to this event when the animation starts.
  • onProgress: This event is triggered multiple times as the keyboard is displayed or dismissed from the screen. It’s called every time the WindowInsetsCompat changes.
  • onEnd: This event triggers after the animation ends. You can use it to clean up any allocated resources or make any UI view reflect this new state.

Now you’ve seen how to make your UI smoothly adapt to any keyboard change. Take a look at which additional events could cause the same trigger.