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?
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.
Everything seems perfect! What if you run it on a device with Android 11?
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:
- You declare these two fields globally because you’ll use them later in a method that handles the keyboard animations.
- To guarantee compatibility with previous Android versions, you give precedence to the appcompat APIs.
- 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.
- 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.
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.
Everything fits perfectly – well done! :]
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.
-
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. - You can use two modes here:
DISPATCH_MODE_CONTINUE_ON_SUBTREE
andDISPATCH_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. - 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. - Every time there’s a change on WindowInsetsCompat,
onProgress
is called and you need to update theroot
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. - With these new values you update the margins, so the UI will synchronize with the keyboard animation.
- 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.
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.