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 3 of this article. Click here to view the first page.

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

Interacting With the Keyboard

In this section, you’ll implement a new feature. When the user scrolls up in the RecyclerView, you’ll push the keyboard at the same speed until it’s fully opened. If it’s visible, swiping down the list will have the opposite behavior, and the keyboard will close.

There are a couple of ways to achieve this. You could use any component that supports scrolling, such as ScrollView or NestedScrollView. In this, case you’ll use RecyclerView.

To open or close the keyboard while the user scrolls through the list, you need to choose a component that supports this behavior:

  • Scroll direction: Depending on if the user’s scroll direction – up or down. In this case, you want to open or close, respectively.
  • Overscroll: The list might not be moving since the user already scrolled until its limit. The user is still swiping his finger across the screen, expecting to see a corresponding action. Because of this, the component you’ll choose needs to support this behavior.
  • Detect when motion starts and stops: Maybe you want to detect when the keyboard animation should start and end. If the user scrolls a bit, you want to open or close the keyboard fully, so it’s important to understand when the movement ends.

To achieve this, you’re going to use the LinearLayoutManager, which supports all of the above functionalities. Go to RWCompat11.kt and update createLinearLayoutManager to:

@RequiresApi(Build.VERSION_CODES.R)
fun createLinearLayoutManager(context: Context, view: View): LinearLayoutManager {
  var scrolledY = 0
  var scrollToOpenKeyboard = false
 
  return object : LinearLayoutManager(context) {
    var visible = false
  }
}

In this method, a couple of fields are already declared:

  • scrolledY: Holds the distance the user dragged in pixels.
  • scrollToOpenKeyboard: True if the user is scrolling up to open the keyboard or false if they’re scrolling down to close it.
  • visible: The initial state of the keyboard when the user started this action.

For now, you’re returning an instance of the LinearLayoutManager. Nevertheless, to make all the calculations needed you’ll need to override two different methods:

  • onScrollStateChanged: Notifies when the user started scrolling through the list and when he finished.
  • scrollVerticallyById: Triggered while the user is scrolling. This lets you synchronize the keyboard animation along with the list the user is scrolling.