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