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

Observing Scroll States

Now override onScrollStateChanged. Add this method inside the LinearLayoutManager declaration:

override fun onScrollStateChanged(state: Int) {
  //1
  if (state == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
    //2
    visible = view.rootWindowInsets?.isVisible(WindowInsetsCompat.Type.ime()) == true
    //3
    if (visible) {
      scrolledY = view.rootWindowInsets?.getInsets(WindowInsetsCompat.Type.ime())!!.bottom
    }
    //4
    createWindowInsetsAnimation()
    //5
  } else if (state == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) {
    //6
    scrolledY = 0
    animationController?.finish(scrollToOpenKeyboard)
  }
 
  super.onScrollStateChanged(state)
}

If Android studio prompts for imports, import android.widget.AbsListView. Ignore the missing methods for now.

Here’s what this code does:

  1. When the user initially touches the list they trigger onScrollStateChanged with the value SCROLL_STATE_TOUCH_SCROLL.
  2. To understand if the keyboard is going to close or open, you need to save its initial state. If it’s open, the corresponding action is to close the keyboard. Conversely, if it’s closed, the corresponding action will open it.
  3. If it’s already visible you need to initialize scrolledY with the IME’s current bottom position, otherwise part of the UI will be covered. This value will be important later since it also influences the value of scrollToOpenKeyboard which defines if the keyboard’s final action is to open or close.
  4. In this method, you define the animation controller listener that’s used in the animation.
  5. After the user finishes the action it’s time to clean up any used resource and finish the animation.
  6. If the keyboard isn’t entirely visible, because the user scrolled a small portion of the screen, this call is responsible for finishing this action.

In this code block, you saw there’s a call to a method you haven’t added: createWindowInsetsAnimation. In the same class, after the declaration of createLinearLayoutManager add:

@RequiresApi(Build.VERSION_CODES.R)
private fun createWindowInsetsAnimation() {
  view.windowInsetsController?.controlWindowInsetsAnimation(
    WindowInsetsCompat.Type.ime(), //types
    -1,                            //durationMillis
    LinearInterpolator(),          //interpolator
    CancellationSignal(),          //cancellationSignal
    animationControlListener       //listener
 )
}

Take note of the import statements to use:
import android.os.CancellationSignal
import android.view.animation.LinearInterpolator

This adds a controller to the inset you want to animate. This method receives the following arguments:

  • types: The types of inset your app wants to control. In this case, since it’s the keyboard you’re going to set it as ime.
  • durationMillis: The duration of the animation. Since the keyboard is going to animate while the user drags the list, which depends on an arbitrary action, you disable the animation by setting it to -1.
  • interpolator: The interpolator used for the animation. In this case, you’re going to use LinearInterpolator.
  • cancellationSignal: Used to cancel the animation and return to the previous state. Since in this scenario the behavior selected is to finish the animation you won’t use this.
  • listener: The animation controller listener that’s called when the windows are ready to animate or the operation cancels or finishes.

Handling Animations

Now that you defined the controlWindowInsetsAnimation you’ll need to declare the animationControlListener used in this method. At the top of RWCompat11 class, just before setUiWindowInsets, add:

 
private val animationControlListener: WindowInsetsAnimationControlListener by lazy {
  @RequiresApi(Build.VERSION_CODES.R)
  object : WindowInsetsAnimationControlListener {
 
    override fun onReady(
      controller: WindowInsetsAnimationController,
      types: Int
    ) {
      animationController = controller
    }
 
    override fun onFinished(controller: WindowInsetsAnimationController) {
      animationController = null
    }
 
    override fun onCancelled(controller: WindowInsetsAnimationController?) {
      animationController = null
    }
  }
}

When the keyboard is ready to animate, you call onReady with the animationController you’ll use to pull or pop the keyboard to or from the screen. Here, the animationController updates with this new reference to use on LinearLayoutManager methods. If the action is either canceled or finished, it cleans all resources, since they’re no longer necessary.

Before going to the last method, declare the animationController field just before the animationControlListener:

private var animationController: WindowInsetsAnimationController? = null

Finally, go back to createLinearLayoutManager. You’ve already declared onScrollStateChanged where the keyboard animation is set up and finished. Now it’s time to create the animation itself.

Override the scrollVerticallyBy:

override fun scrollVerticallyBy(dy: Int, recycler: Recycler, state: State): Int {
  //1
  scrollToOpenKeyboard = scrolledY < scrolledY + dy
  //2
  scrolledY += dy
  //3
  if (scrolledY < 0) {
    scrolledY = 0
  }
  //4
  animationController?.setInsetsAndAlpha(
    Insets.of(0, 0, 0, scrolledY),
    1f,
    0f
  )
 
  return super.scrollVerticallyBy(dy, recycler, state)
}

When prompted for imports, import androidx.recyclerview.widget.RecyclerView.* and android.graphics.Insets.

In the code above:

  1. Since the user can scroll up and down the list you can't rely on the keyboard's initial visibility state. To know if it should open or close the keyboard, scrollToOpenKeyboard calculates the user's swipe direction based on scrolledY and dy. If the last scroll was upwards to the beginning of the list the keyboard will show, otherwise it will hide.
  2. dy contains the distance from scrollVerticallyBy event. To know the total distance scrolled you have to add this reference to a variable set inside the LinearLayoutManager scope: scrolledY.
  3. In case the scrolledY is negative, the value will be set to 0 since it's not possible to move the keyboard to a negative value.
  4. Finally, setInsetsAndAlpha defines the movement that needs to occur on the IME window. In this case, you only need to define the bottom value so all the other values are set to 0. The 1f corresponds to the value set for the alpha property, which is set to the maximum to avoid having any transparency. 0f is the animation progress.

Now that you've defined everything, it's time to compile and run the app!

Opening and closing the keyboard by scrolling through the notes list

Beautiful, right? :]

Where to Go From Here?

Congratulations! You learned how to create a seamless animation when launching the keyboard.

You can download the completed project files by clicking the Download Materials button at the top or bottom of the tutorial.

Android 10 and 11 have many new features you can use to empower your app! For a fun challenge, try the Bubbles tutorial. Consider learning about augmented reality apps in ARCore with Kotlin. Does your app deal with files? Don't forget to make it ready for Scoped Storage.

If you have any questions or comments, please join the discussion below.