preface

Nice to meet you! πŸ‘‹

As an Android developer, I don’t know if you have this problem:

  • Don’t know how to get the status bar, navigation bar and soft keyboard height

  • Visual and interactive conflicts occur after content is drawn to the status bar and navigation bar areas

  • Do not understand the principle of edge-to-edge adaptation

  • Not sure android: fitsSystemWindows this attribute

In September Pure Writing developer Drakeet Shared a video about WindowInsets on The Knowledge Planet of Throwlines, which explains the following:

  • WindowInsetsWhat is?
  • WindowInsetsHow is it distributed?
  • How to adapt edge-to-edge?
  • How to accurately monitor the soft keyboard eject and get the height of the soft keyboard?
  • How to customize the response behavior after the soft keyboard is popped up?

This article is a supplementary to that video, mostly from Chris Banes’ 2017 talk Becoming a Master Window Fitter πŸ”§ (moved to Station B).

Here’s what you’ll learn from reading this article:

  • System barChanges in capabilities from version to version
  • App draws the content toStatus bar ε’Œ Navigation barPrinciple behind
  • fitsSystemWindows 与 WindowInsetsThe concept of
  • WindowInsetsDistribution logic of
  • To deal withWindowInsetsBest practices of

Due to space, there is not much source code analysis in this article. The underlying implementation principles will be described in a later article. Those of you who only care about best practices can jump right to the best Practices section.

Let’s get started

What is a Window

In the Android Detail: Window article, we discussed the core concepts of Android Window and came to a conclusion:

On Android, the API that exposes developers to the UI is mWindowManager.addView(rootView, windowParams);

In short, every View on the Android screen is inside the Window.

  • Each Activity has its own Window (PhoneWindow),Activity#getWindow()
  • Dialog also has its own Window,Dialog#getWindow()
  • PopupWindow, Toast also passWindowManager#addViewPut the View on Widnow

What is the Insets

In addition to the content drawn by the developer app on the screen, there are the system’s Insets (inserts). The Insets area describes which parts of the screen will intersect with the system UI. Starus bar or Navigation bar:

Common Insets include:

  • STATUS_BAR, used to display system time, power, wifi and other information
  • NAVIGATION_BAR, virtual navigation bar (different from the entity of the three diamond keys), the shape of the three diamond keys navigation, gesture navigation two. (Some devices, such as TVS, have no navigation bar)
  • IME, soft keyboard for input text

STATUS_BAR and NAVIGATION_BAR are also called System bar.

If the developer draws something within the system UI area, visual and gesture conflicts can occur. Insets allow developers to move views inwards from the edge of the screen to a suitable location.

In the source code, the Insets object has four int values that describe the offset of the four sides of the rectangle:

πŸ“’ Note: do not confuse the top, bottom, left, and right of Insets with Rect. The former describes the offset and the latter is the coordinate.

For more detailed information on Insets, check out this article.

SetSystemUiVisibility and WTFs

The View source code has a method called setSystemUiVisibility(), which has been deprecated in Android 11, but in keeping with the style of this column, we’ll cover.

Some scenario developers may want the content of the app to be drawn to the area of the status bar or navigation bar to provide better user experience, so the system provides the setSystemUiVisibility method, to which developers can pass different flags to respond to different scenarios.

These Flags are called Window Transform Flags, or WTFs (funny Face 😏), and they were also deprecated in Android 11. Common flags are as follows:

If you want to see the effect of these flags, you can go here.

System Bar Capability change history

The Android 4.4 before

The user content is displayed between the System bar, which is the area in the red box below:

Developers can use the setSystemUiVisibility method to draw content behind the status bar, as shown in the red box below:

The Android 4.4

Android 4.4 introduces the Android: windowTranslucentStatus and Android: windowTranslucentNavigation, allows developers to set the System bar into a transparent:

The System bar background is drawn by WindowManager (using the Window flag)

The Android 5.0

Version before the System bar is drawn by the WindowManager, in Android 5.0, introduced the Android: windowDrawsSystemBarBackgrounds, When windowDrawsSystemBarBackgrounds to true (the default), the System background of bar within the Window. The diagram below:

Developers can call the Window method to set the color of the System bar:

πŸ“’ note: windowTranslucentStatus and windowTranslucentNavigation than set custom colors for the System bar higher priority.

When windowTranslucentStatus or windowTranslucentNavigation can cause windowDrawsSystemBarBackgrounds after is set to true to false, System Bar background is taken over by WindowManager.

Since Android 5.0, when windowDrawsSystemBarBackgrounds is true, the System bar as part of the window. In other words, the DecorView (FrameLayout subclass) has three child views: the LinearLayout that displays the App content, and the Status bar and Navigation bar.

By default, App content is displayed in the middle of the System bar.

In theory, the LinearLayout that displays App content should fill the screen. PaddingTop and marginBottom are used to reserve space for the System bar.

How is the content area of the App drawn behind the System bar? The LinearLayout, without padding or margin, fills the screen:

Android 10

As more And more Android devices move beyond the 16:9 limit on all-screen devices, Android 10 introduces a new navigation mode: gesture navigation.

The new gesture Navigation bar is the same as the original Navigation bar with three diamond keys, but at a reduced height.

If the Navigation bar is transparent, the “little white bar” at the bottom can change color dynamically following the background (same as iOS, no idea who copied whom 🀣)

Android 11

Android 11 introduced the WindowInsetsAnimation to allow you to listen to Insets changes, making the user experience more silky.

summary

In order to facilitate developers to use the device screen to draw content more reasonably, Android has continuously iterated the API controlled by System Bar in successive versions, with more and more perfect functions.

Visual and gesture conflicts should be considered when drawing App content behind the System bar.

* WidowInsets and fitsSystemWindows ** Are two API options for avoiding visual conflicts between App content area and System Bar.

WidowInsets

WindowInsets describes a set of Window Content Insets. New types of Insets may be added in the future. The types of Insets available are:

Using bit operations to manage state is a very common and efficient way to do this. If you’re not familiar with this section, follow KunMinX’s article to get started

System bar includes Status bar, Navigation bar, and Caption bar, but does not include iME

OnApplyWindowInsets and setOnApplyWindowInsetsListener

Developers can pass in the custom View to rewrite onApplyWindowInsets () method or call setOnApplyWindowInsetsListener () to monitor WindowInsets change, Resolve conflicts by adding margin or padding to the View.

These two methods are mutually exclusive, not when there is a OnApplyWindowInsetsListener perform onApplyWindowInsets:

Developers can also can be OnApplyWindowInsetsListener manual call onApplyWindowInsets that both the two methods is carried out.

WindowInsets distribution

As mentioned earlier, visual and gesture conflicts can occur if the developer draws something in the system UI area. Developers can use Insets to move a view inwards from the edge of the screen to a suitable location, where view #onApplyWindowInsets() is called. So how do these Insets get distributed to the View?

The author abstracts the Android View tree into an N-fork tree in the paper of View event distribution mechanism, Large workplace PUA scene.

Like View event distribution, WindowInsets distribution is an n-tree traversal:

Start from the root node of the n-tree (DecoView) and distribute to child views in depth-first mode.

Android 10 and 11 continuous changed the two versions of the official Android ViewGroup# dispatchApplyWindowInsets () logic analytical introduction (specific in our source code).

If app targetSdkVersion < 30, if a node consumes Insets, all nodes that are not iterated will not receive WindowInsets.

If a node consumes Insets, all children of that node will not receive WindowInsets distribution when the app is running on devices with Android versions higher than 11 and targetSdkVersion >= 30.

One problem with the distribution of older versions is that two Windows sets of the same class cannot be consumed at the same time.

We can think of Level2-1 and Level2-2 as top navigation and bottom navigation, using the old logic that when Level2-1 consumed the WindowInsets, the other View wouldn’t get a chance.

summary

  • Since developers can draw App content to the point where it intersects the system UI, the authorities provide developers with a way to resolve visual conflicts,WindowInsets
  • Developers can rewriteView#onApplyWindowInsets ζˆ– View#setOnApplyWindowInsetsListenerTo position the system UI based on the WindowInsets (setting the padding or margin on the view).
  • In the next sectionfitsSystemWindowsThe default behavior is also passedonApplyWindowInsetsThe implementation.

fitsSystemWindows

SetFitsSystemWindows is API 14 after joining method in the View, the corresponding XML attributes are android: fitsSystemWindows

The default behavior of fitsSystemWindows is to reserve space for the System bar using the padding. As mentioned earlier in the DecorView LinearLayout, its paddingTop is affected by fitsSystemWindows = True.

By default, the child view of a DecorView is inflate Screen_simple.xml.

So how is the padding set?

View#onApplyWindowInsets() calls fitsSystemWindows to internalSetPadding() :

πŸ“’ Note: This invalidates the padding defined by the developer in the XML.

The fitsSystemWindows API is confusing to many developers, not least because fitsSystemWindows often does not use the default behavior, such as DrawerLayout and CoordinatorLayout.

DrawerLayout

DrawerLayout fitsSystemWindow = true

  • API > 21 set when setSystemUiVisibility (SYSTEM_UI_FLAG_LAYOUT_STABLE | SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)

  • * * onMeasure () when the child view dispatchApplyWindowInsets () (* * father view consumption after WindowInsets son view receives less than normal distribution)

  • OnDraw () calls setStatusBackground(? android:colorPrimaryDark)

CoordinatorLayout

CoordinatorLayout fitsSystemWindow = True:

  • Set when API > 21setSystemUiVisibility(SYSTEM_UI_FLAG_LAYOUT_STABLE | SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
  • According to the needsetStatusBackground
  • Allows a child View of the behavior to be set to intercept and respondWindowInsetsThe change of the

summary

Here’s what you must know about fitsSystemWindows:

  • FitsSystemWindows is depth-first (we can think of the view tree as an n-fork tree), and the first view set to fitsSystemWindows will consume insets and affect the view;

  • The padding is set before the view layout, so don’t mistake it for knowing where the view is when you set the padding

  • The padding of the developer’s XML or view initialization Settings is overwritten

  • AppBarLayout, CoordinatorLayout, DrawerLayout and other views customize the behavior of fitsSystemWindows

Best practices for handling WindowInsets

Use the Compat API provided by Jetpack

Androidx. core in the Android Jetpack component library provides a number of apis that are compatible with older versions of Compat, such as ViewCompat, WindowInsetsCompat, WindowInsetsControllerCompat and so on.

Below is ViewCompat# getWindowInsetsController method, is used to obtain WindowInsetsController, and less compatible version:

Get WindowInsets

Using ViewCompat. GetRootWindowInsets WindowInsets (view). Please note:

  • This method returns the original Insets distributed to the view tree

  • Insets are only available in View Attached

  • API 20 and below always return false

Gets the height of the System bar and the soft keyboard

❌ Incorrect usage

πŸ™…πŸ»β™€οΈ Do not fix the status bar height

Different Android Versionsstatus barThe height is different! Different devices may also customize their own heights.

πŸ™…πŸ»β™€οΈ Reads internal system resources

Dimens.xml of the Framework stores a list of internal system resources.

What if the internal resource name of the system changes?

“Wild path” code may be effective, but it is not robust.

βœ… Correct usage

  1. To obtainWindowInsets
  2. throughWindowInsets#getInsets(type)Get Insets
  3. Can be obtained by Insets. Top or Insets. BottomSystem barhighly

To be compatible with older versions, we use the Compat API:

  1. val windowInsetsCompat = ViewCompat.getRootWindowInsets(view)Get WindowInsets
  2. val insets = windowInsetsCompat? .getInsets(WindowInsetsCompat.Type.statusBars())Get Insets
  3. insets? .top ζˆ– insets? .bottomTo obtainSystem barhighly
ViewCompat.getRootWindowInsets(window.decorView)?.getInsets(WindowInsetsCompat.Type.statusBars())?.top
ViewCompat.getRootWindowInsets(window.decorView)?.getInsets(WindowInsetsCompat.Type.statusBars())?.bottom
Copy the code

When the System bar hidden getInsets () to obtain the height is zero, if you want to hide status based on can obtain highly, can use getInsetsIgnoringVisibility () method

ViewCompat.getRootWindowInsets(window.decorView)?.getInsetsIgnoringVisibility(WindowInsetsCompat.Type.statusBars())?.top  ViewCompat.getRootWindowInsets(window.decorView)?.getInsetsIgnoringVisibility(WindowInsetsCompat.Type.statusBars())?.bo ttomCopy the code

WindowInsetsController

Android 30 introduces the WindowInsetsController to control WindowInsets. Key features include:

  • The System Bar is displayed or hidden

  • Sets whether the System Bar foreground (such as the text icon in the status bar) is light or dark

  • Control the insets animation frame by frame, for example to make the soft keyboard pop more silky

Hide the System Bar

// Whether the soft keyboard is visibleViewCompat.getRootWindowInsets(view)? .isVisible(WindowInsetsCompat.Type.statusBars()) ? :true
// Display the status bar
ViewCompat.getWindowInsetsController(view).show(WindowInsetsCompat.Type.statusBars())
// Hide status bar
ViewCompat.getWindowInsetsController(view).hide(WindowInsetsCompat.Type.statusBars())

// Whether the navigation bar is visibleViewCompat.getRootWindowInsets(view)? .isVisible(WindowInsetsCompat.Type.navigationBars()) ? :true
// Displays the navigation bar
ViewCompat.getWindowInsetsController(view).show(WindowInsetsCompat.Type.navigationBars())
// Hide the navigation
ViewCompat.getWindowInsetsController(view).hide(WindowInsetsCompat.Type.navigationBars())

// Whether the soft keyboard is visibleViewCompat.getRootWindowInsets(view)? .isVisible(WindowInsetsCompat.Type.ime()) ? :false
// Display the soft keyboard
ViewCompat.getWindowInsetsController(view).show(WindowInsetsCompat.Type.ime())
// Hide the soft keyboard
ViewCompat.getWindowInsetsController(view).hide(WindowInsetsCompat.Type.ime())
Copy the code

Set the System bar foreground color to bright/dark

ViewCompat.getWindowInsetsController(view).isAppearanceLightStatusBars = isLight
ViewCompat.getWindowInsetsController(view).isAppearanceLightNavigationBars = isLight
Copy the code

Adaptive edge – to – edge

What is the edge – to – edge? As shown below, the drawing range of the app content starts from below the top status bar and extends to above the bottom navigation bar:

The official documentation for edge-to-edge adaptation is complete and consists of three main steps:

// 1. Make the content area full screen
WindowCompat.setDecorFitsSystemWindows(window, false)

// 2. Set System bar transparency
window.statusBarColor = Color.TRANSPARENT
window.navigationBarColor = Color.TRANSPARENT

// 3. Handle insets with possible visual conflicts
ViewCompat.setOnApplyWindowInsetsListener(view) { view, windowInsets ->
  val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
  // Change margin here, can also set padding, depending on the situation
  view.updateLayoutParams<MarginLayoutParams> {
    	topMargin = insets.top
      leftMargin = insets.left
      bottomMargin = insets.bottom
      rightMargin = insets.right
  }
  WindowInsetsCompat.CONSUMED
}
Copy the code

Note: When dealing with the insets, ensure that the calculation is idempotent, i.e., the result should be the same multiple times, otherwise the margin/padding will get bigger and bigger!

Handling insets can also be done by overriding View#onApplyWindowInsets.

conclusion

  • As Android continues to iterate, developers are able to use screen real estate more fully and draw content behind the system UI.
  • Android uses Insets to describe areas where the system UI intersects the screen and developers can use themfitsSystemWindows ε’Œ WindowInsetsTo deal with visual and gesture conflicts;
  • WindowInsetsDistribution basistargetSDKVersionDifferent and slightly different;
  • fitsSystemWindowsThe default behavior of the padding isSystem barIn essence, use the WindowInsets to deal with visual conflicts.
  • Some custom views such as DrawerLayout will changefitsSystemWindowsDefault behavior of;
  • To deal withWindowInsetsYou can use Jetpackandroidx.coreA list of Compat classes provided;
  • Keep in mind to getStatus barHeight of correct posture, and avoid incorrect usage;
  • Edge-to-edge is adapted to provide better user experience

Recommended reading and reference resources

  • Open the full screen experience | gestures navigation (a)
  • Process visual conflict | gestures navigation (2)
  • How to deal with the gesture conflict | navigation series (3)
  • Immersion mode | gestures to navigate serial (4)
  • Why would I want to fitsSystemWindows?
  • Listeners to the notice
  • Becoming a master window fitterπŸ”§

About me

People love to do things that give them positive feedback. If this article is helpful to you, please click πŸ‘. This is very important to me

I am Flywith24. Only through discussion with others can we know whether our own experience is real or not. By communicating with others on wechat, we can make progress together.

  • The Denver nuggets
  • Small column
  • Github
  • Wechat (official account of the same name) : Flywith24

Follow the public account, click on the bottom to contact me -> Knowledge planet to join the free knowledge planet