One of the new features in Android 11 is the ability for apps to create seamless transitions between opening and closing soft keyboards on the screen, a feature that comes from a number of improvements to the WindowInsets API.

There are two examples of this feature on Android 11 — it’s already integrated into the Google Search app and Messages app:

Two examples of soft keyboard animation effects in Android 11: Google Search app (left), Messages (right)

Let’s take a look at how to add this user experience to your application. There are three steps:

  • First, we need edge-to-edge.
  • Second, the app needs to react to the edge animation.
  • Finally, the third step is to apply control and use edge animation in the appropriate scene.

Each of the above steps is linked, so we’ll cover them separately in different articles. In the first installment of this series, we’ll cover how to implement edge-to-edge and the API changes in Android 11.

Edge-to-edge implementation

Last year we introduced an implement the concept of “edge to edge”, this method can make the application depth using Android gesture of 10 navigation: open the full screen experience | gestures navigation (a).

To recap, implementing edge-to-edge renders your application behind the system status bar, as shown in the figure above.

To quote myself from last year:

After a full edge-to-edge screen experience, the system bar is overlaid in front of the app content. Apps are also able to deliver a more impactful experience with larger content.

What does implementing edge-to-edge have to do with soft keyboards?

In fact, implementing edge-to-edge isn’t just about rendering after the status bar and navigation bar. The application itself needs to start taking care of the parts of the system UI that overlap with the application.

As we mentioned earlier, the two most intuitive examples are the status bar and the navigation bar. In addition to the soft keyboard, sometimes called IME (Input Method Editor), this is another system UI that we need to understand.

How does the application implement edge-to-edge?

If we recall last year’s introduction, achieving edge-to-edge can be divided into three steps:

  • Change the color of the system bar
  • Set the full-screen layout
  • Dealing with visual conflict

We’re going to skip step 1 because this part hasn’t changed since last year. Steps 2 and 3 in the tutorial have some changes for Android 11, so let’s take a look.

#2: Set the full-screen layout

In the previous step, the application needed to use the systemUiVisibility API and some parameters to set the full-screen layout:

view.systemUiVisibility = 
    // Tell the system how Windows wants to lay out content in extreme cases. Check the documentation for more specific information.
    View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
    // Tell the system how the window wants to lay out the content with the navigation bar hidden.
    View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
Copy the code

If the target SDK version of your project setup has been upgraded to 30 and you use this API, you will find that these apis have been marked deprecated.

They have been a Window called setDecorFitsSystemWindows () function replaces:

// Notification window, we will handle any system window (not decor)
window.setDecorFitsSystemWindows(false)
// Or you can use Windows COMPat in AndroidX V1.5.0-alpha02
WindowCompat.setDecorFitsSystemWindows(window, false)
Copy the code

In place of those parameters is a Boolean value false, which means that the application will handle any system window adaptation (in other words, full screen).

We also have a Jetpack version of this function in Windows COMPat, and the v1.5.0-Alpha02 version of the Androidx. core library also includes this function.

That’s the change in step 2.

#3: Deal with visual conflict

Now let’s take a look at step 3: Avoid overlapping with the system UI, or using the window edge to determine how to move the application’s content to avoid conflicts with the system UI. In Android, the margin area is accessible through the WindowInsets class and the WindowInsetsCompat in AndroidX.

If we look at pre-API 30 WindowInsets, the most common type of side pane is system Window side pane. These margins include the status bar, the navigation bar, and the soft keyboard when opened.

In order to use WindowInsets, you usually need to add OnApplyWindowInsetsListener on one view, and in this function processing incoming line area:

ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets ->
    v.updatePadding(bottom = insets.systemWindowInsets.bottom)
    // Return the margins so that they can continue in the view tree
    insets
}
Copy the code

In this example, we get the system window margin and then update the inner margin of the view, which is a common application scenario.

There are also some other types of margins, such as the gesture margin recently added to Android 10:

ViewCompat.setOnApplyWindowInsetsListener(v) { view, windowInsets ->
    val sysWindow = windowInsets.systemWindowInsets
    val stable = windowInsets.stableInsets
    val systemGestures = windowInsets.systemGestureInsets
    val tappableElement = windowInsets.tappableElementInsets
}
Copy the code

Like the systemUiVisibility API, many WindowInsets have been deprecated in favor of new functions to query different types of edge areas:

  • GetInsets (type: Int) returns the visible margin of the specified type.
  • GetInsetsIgnoringVisibility (type: Int) returns all edge line area, regardless of whether they are visible.
  • IsVisible (type: Int) returns true if the specified type isVisible.

We’ve just mentioned “types” a few times. They’re defined as functions in the Windowinsets. Type class, and each function returns an integer identifier. We will also show how to use the OR bit operation to query the combined types later.

All of these apis have been added to the WindowInsetsCompat in AndroidX Core and are forward-compatible with API 14 (see the release notes for more information).

Again, if we update the previous examples with the new API, they become:

ViewCompat.setOnApplyWindowInsetsListener(...) { view, insets ->
-    val sysWindow = insets.systemWindowInsets
+    val sysWindow = insets.getInsets(Type.systemBars() or Type.ime())
-    val stable = insets.stableInsets
+    val stable = insets.getInsetsIgnoringVisibility(Type.systemBars())
-    val systemGestures = insets.systemGestureInsets
+    val systemGestures = insets.getInsets(Type.systemGestures())
-    val tappableElement = insets.tappableElementInsets
+    val tappableElement = insets.getInsets(Type.tappableElement())
}
Copy the code

Soft keyboard type ⌨️

By now those keen 👀 are probably staring at this list of types, especially the soft keyboard types.

After a decade of delay, we can finally answer the StackOverflow question about how to view the visibility of soft keyboards. 🎉

  • How do I view the visibility of the soft keyboard on Android?

To get the visibility of the current soft keyboard, we can get the edge of the root window, then execute isVisible() and pass in the IME type.

Similarly, if we wanted to find out the height, we could do it in the same way:

val insets = ViewCompat.getRootWindowInsets(view)
val imeVisible = insets.isVisible(Type.ime())
val imeHeight = insets.getInsets(Type.ime()).bottom
Copy the code

If we need to monitor the change of the soft keyboard, we can use OnApplyWindowInsetsListener as usual, and use the same function:

ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets ->
    val imeVisible = insets.isVisible(Type.ime())
    val imeHeight = insets.getInsets(Type.ime()).bottom
}
Copy the code

Hide or display the soft keyboard

Now that we’re answering questions on StackOverflow, take a look at this 11-year-old question about how to turn off soft keyboards.

  • How do I turn off/hide the Android Soft Keyboard?

This time we’ll introduce a new API for Android 11 called WindowInsetsController.

The application can obtain a controller from any view, and then we can show or hide the soft keyboard by passing in the IME type and executing show() or hide() :

val controller = view.windowInsetsController
// Display soft keyboard (IME)
controller.show(Type.ime())
// Hide the soft keyboard
controller.hide(Type.ime())
Copy the code

However, this controller does more than just hide and show the soft keyboard…

WindowInsetsController

As mentioned earlier, some of the View.system_UI_ * flags have been deprecated in Android 11 and replaced by new apis. There are also several view. SYSTEM_UI flags that are used to change the appearance and visibility of the system UI, including:

  • View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
  • View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
  • View.SYSTEM_UI_FLAG_LAYOUT_STABLE
  • View.SYSTEM_UI_FLAG_LOW_PROFILE
  • View.SYSTEM_UI_FLAG_FULLSCREEN
  • View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
  • View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
  • View.SYSTEM_UI_FLAG_IMMERSIVE
  • View.SYSTEM_UI_FLAG_VISIBLE
  • View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
  • View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR

Like the previous flags, these were also deprecated in API 30 and replaced by the API in WindowInsetsController.

Instead of going through all of these flag changes, we’ll look at a few common application scenarios to show how to update these flags:

Immersion mode

As shown, the drawing application hides the system UI to maximize the drawing area:

Markers is used to display the hidden system UI

To achieve this effect, we use the WindowInsetsController to perform the hide() and show() functions as before, but this time we’ll pass in the system bar type:

val controller = view.windowInsetsController

// When we want to hide the system bar
controller.hide(Type.systemBars())

// When we want to display the system bar
controller.show(Type.systemBars())
Copy the code

The app uses immersion mode to allow the user to swipe to recall the system bar when it is hidden. To do this, we use WindowInsetsController and change setSystemBarsBehavior() to BEHAVIOR_SHOW_BARS_BY_SWIPE:

val controller = view.windowInsetsController

// Now start immersion..
controller.setSystemBarsBehavior(
    WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE
)

When we want to hide the system bar
controller.hide(Type.systemBars())
Copy the code

Similarly, if you were using adsorption immersion mode, this can now be implemented with BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE:

val controller = view.windowInsetsController

// Now for adsorption immersion...
controller.setSystemBarsBehavior(
    BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
)

When we want to hide the system bar
controller.hide(Type.systemBars())
Copy the code

The color of the status bar content

The next application scenario revolves around the color of the status bar content. You will see the following two applications:

Two apps, the one on the left uses a dark background and the one on the right uses a light background

The app on the left uses a dark background for the status bar, while its content is light, such as the time and ICONS. But if we want to implement a light-colored status bar background with dark content, as shown on the right, we can also use the WindowInsetsController.

To do this, use the setSystemBarsAppearance() function and pass in the APPEARANCE_LIGHT_STATUS_BARS value:

val controller = view.windowInsetsController

// Enable light-colored status bar content
controller.setSystemBarsAppearance(
    APPEARANCE_LIGHT_STATUS_BARS, // value
    APPEARANCE_LIGHT_STATUS_BARS // mask
)
Copy the code

But if you want to set up a dark status bar, you can pass in 0 instead of clearing that value.

Note: you can also in the theme by setting the android: windowLightStatusBar achieve these results. This approach is probably better if you know that the value will not change.

The APPEARANCE_LIGHT_NAVIGATION_BARS flag can provide similar functionality for navigation bars.

WindowInsetsController on AndroidX?

Unfortunately, the Jetpack version of the API isn’t available yet, and we’re working on it, so stay tuned.

Implement edge-to-edge: ✔️

Our first step is done. In the next article in this series, we’ll look at the second step: applying responsive animation to edge lining. Stay tuned.