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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Window Insets and Keyboard Animations Tutorial for Android 11
25 mins
- Getting Started
- Understanding the Project Structure
- Understanding Window Insets
- Getting to Know the Keyboard
- What’s Available on Older APIs
- Launching the Keyboard
- Checking if the Keyboard is Visible
- Getting the Keyboard Height
- What’s New in Android 11
- Preparing Your App for Keyboard Animations
- Handling System Windows
- Animating the Keyboard
- WindowInsets Animation Lifecycle
- Interacting With the Keyboard
- Observing Scroll States
- Handling Animations
- Where to Go From Here?
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.
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:
- When the user initially touches the list they trigger
onScrollStateChanged
with the valueSCROLL_STATE_TOUCH_SCROLL
. - 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.
- 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 ofscrollToOpenKeyboard
which defines if the keyboard’s final action is to open or close. - In this method, you define the animation controller listener that’s used in the animation.
- After the user finishes the action it’s time to clean up any used resource and finish the animation.
- 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:
- 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 onscrolledY
anddy
. If the last scroll was upwards to the beginning of the list the keyboard will show, otherwise it will hide. -
dy
contains the distance fromscrollVerticallyBy
event. To know the total distance scrolled you have to add this reference to a variable set inside the LinearLayoutManager scope:scrolledY
. - In case the
scrolledY
is negative, the value will be set to0
since it's not possible to move the keyboard to a negative value. - 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 to0
. The1f
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!
Beautiful, right? :]