Window Insets for Android Compose

Window Insets for Android Compose

In addition to the content area of the app, some other fixed elements will be displayed on the mobile phone screen, including the status bar at the top, bangs, navigation bar at the bottom, and input method keyboard,
They are the UI of the system, also known as Insets

As shown in the figure:

The status bar at the top is usually used to display notifications, equipment status, etc;
The bottom navigation bar usually displays three navigation buttons: back, home, recent
They are collectively called system bars

The Insets class of Android describes the offset size information. Indeed, we pay more attention to the size information of these system UI in our development

This article introduces that after using Compose as UI, with the help of Accompanist Insets: https://google.github.io/accompanist/insets/.
How to do several common situations related to Insets

Content area

Going Edge-to-Edge

Create a new App written in Compose. By default, it is an ordinary App without Inset processing

Can the content of the app be displayed in these system bars areas in the form of edge to edge?
Yes, of course

Two concepts are clarified here:

  • Edge to edge: the content of app is drawn after system bars, which still exists in translucent form
  • Different from the "immersive mode", the immersive mode needs to hide the system bars, and the app content is completely full screen. It is mostly used for watching videos, painting and other scenes

The content area extends to system bars

It's easy to extend the content to the status bar and navigation bar areas, just add one line of code:

WindowCompat.setDecorFitsSystemWindows(window, false)

The default value is true, indicating the default behavior: the content of the app will automatically find the embedded area to draw
When set to false, the content of the app will extend to the lower layer of system bars

The difference is shown in the following figure: the left is the default display, and the right is the situation after adding the setting of false flag:

Well, the content is drawn out, but it is blocked

You need it at this time systemuicontroller To change the color:
Add these lines to change your favorite color:

val systemUiController = rememberSystemUiController()
val useDarkIcons = MaterialTheme.colors.isLight

SideEffect {
    systemUiController.setSystemBarsColor(
        color = Color.Green.copy(alpha = 0.1f),
        darkIcons = useDarkIcons
    )
}

system bars is changed here, that is, both status bar and navigation bar are changed There is also a separate method of changing only one
For demo, set the color to transparent green (as shown in the left figure);
Color may be used in normal application scenarios Transparent (as shown in the right figure)

Extended but embedded

After making the UI of several pages, I found that some contents were covered in the status bar and bottom, and the experience was not very good
Can you let out the part with text content?

Therefore, this dependency is added: Insets for Jetpack Compose

The distance between the top and bottom is left in two simple lines:

ProvideWindowInsets {
    Sample1(modifier = Modifier.systemBarsPadding())
}

Wait, if the setting of system bars color is ignored
It looks as like as two peas in the beginning.

So can we delete windowcompat directly Setdecorfitssystemwindows (window, false), just use the default setting?

  • Yes If that's what you need
  • no If you need to draw the app background; If you still have input box processing

If the requirement is to extend the background, the text is embedded
Add different padding to the upper and lower elements respectively:

Column(
        modifier = modifier.fillMaxSize()
                .background(color = Color.Blue.copy(alpha = 0.3f)),
        verticalArrangement = Arrangement.SpaceBetween
) {
    Text(
            modifier = Modifier.fillMaxWidth()
                    .background(color = Color.Yellow.copy(alpha = 0.5f))
                    .statusBarsPadding(),
            text = "Top Text",
            style = MaterialTheme.typography.h2
    )
    Text(text = "Content", style = MaterialTheme.typography.h2)
    Text(
            modifier = Modifier.fillMaxWidth()
                    .navigationBarsPadding()
                    .background(color = Color.Yellow.copy(alpha = 0.5f)),
            text = "Bottom Text",
            style = MaterialTheme.typography.h2
    )
}

After running, it is shown on the right in the figure below:

Note the order of modifier s here. The colors extending from top to bottom are different. The colors extending from bottom to top are actually the colors of columns

On the left is the case of adding insets padding to the overall layout. If system bars is used, the effect is the same as that of the default UI

The details can be customized according to the needs

Padding and content padding of LazyColumn

There is a very long LazyColumn. How should it be displayed under the design of edge to edge?
There are three options:

  1. List full screen: LazyColumn {}
  2. List padding: LazyColumn(modifier = Modifier.systemBarsPadding()) {}
  3. List Content padding:
LazyColumn(
    contentPadding = rememberInsetsPaddingValues(
        insets = LocalWindowInsets.current.systemBars,
        applyTop = true,
        applyBottom = true,
    )
) {}

In fact, the behavior of 1 and 2 is very similar, only the difference of display area size
content padding just adds padding above the first item and below the last item,
In the middle of scrolling, the content can be full screen, and padding will be displayed only at the end or in the end

content padding can better illustrate the situation with dynamic diagram:

Content area processing summary

Insets library provides several modifiers:

  • Modifier.statusBarsPadding()
  • Modifier.navigationBarsPadding()
  • Modifier.systemBarsPadding()
  • Modifier.imePadding()
  • Modifier.navigationBarsWithImePadding()
  • Modifier.cutoutPadding()
    You can use it directly in the layout to get the required padding. For example, statusBarPadding is top and navigationbarspadding is bottom
    This does not need developers to think for themselves

If none of these can meet your needs, you can also directly use the size:

  • Modifier.statusBarsHeight()
  • Modifier.navigationBarsHeight()
  • Modifier.navigationBarsWidth()
    Or more directly use localwindowinsets Current gets the relevant dimensions of the inset type you want

Input box elements and keyboard

On screen keyboard, also known as IME (Input Method Editor),
Generally, clicking the input box will pop up. IME is also an Inset

The input box is blocked by the keyboard

When the input box is in the upper half of the screen, there is basically no need to consider the problem of keyboard occlusion
But when the input box is in the lower half of the screen, we need to make the input box fully displayed without being covered when the keyboard pops up

To solve this problem, we need several things:

  • android:windowSoftInputMode="adjustResize" of Activity indicates that when the keyboard pops up, the Activity will change the layout size, which is extruded
  • Modifier. The use of imepadding adds a bottom padding that is exactly equal to the height of the keyboard to the layout It is usually for the parent layout of the input box, which layer to add depends on the situation
  • If the above two settings still fail to fully display the input box, it may be necessary to add some powerful wake-up behavior

According to this issue This comment,
You can use this Modifier to bring yourself into view when the ui gets the focus

@ExperimentalComposeUiApi
fun Modifier.bringIntoViewAfterImeAnimation(): Modifier = composed {
    val imeInsets = LocalWindowInsets.current.ime
    var focusState by remember { mutableStateOf<FocusState?>(null) }
    val relocationRequester = remember { RelocationRequester() }

    LaunchedEffect(
        imeInsets.isVisible,
        imeInsets.animationInProgress,
        focusState,
        relocationRequester
    ) {
        if (imeInsets.isVisible &&
            !imeInsets.animationInProgress &&
            focusState?.isFocused == true) {
            relocationRequester.bringIntoView()
        }
    }

    relocationRequester(relocationRequester)
        .onFocusChanged { focusState = it }
}

The relocationrequest has been deprecated. The new version of Compose is called BringIntoViewRequester

IME padding calculation and layout

The value of. imePadding() changes. It is 0 without a keyboard. When there is a keyboard, it changes to the keyboard height

To calculate the pop-up height of the keyboard, pay attention to:

  • In the simplest case, use it directly When imePadding() is finished, the bottom padding of the layout will automatically fit with IME
  • If the whole has a height of navigation bar, you can consider using it Navigationbarswithmepadding(), which is the maximum value of IME and navigation bar height
  • If a white note appears on the top of the keyboard, it indicates that there are too many padding. Either there is inner padding in the layout or navigationBarsPadding has been added At this time, you can do a subtraction by yourself
    For example:
LazyColumn(
    contentPadding = PaddingValues(
        bottom = with(LocalDensity.current) {
            LocalWindowInsets.current.ime.bottom.toDp() - innerPadding.bottom
        }.coerceAtLeast(0.dp)
    )
) { /* ... */ }

Where. imePadding is placed is related to what kind of area will be displayed, and the wrapped area will be displayed above the keyboard

For example, there is an interface with input box

Let's set a whole for it Navigationbarswithmepadding(), indicating that when there is no keyboard, the height of the navigation bar is reserved at the bottom. When there is a keyboard, the height of the keyboard is reserved:

Column(
    modifier = Modifier.fillMaxSize().statusBarsPadding().navigationBarsWithImePadding()
        .background(color = Color.Cyan.copy(alpha = 0.2f)),
    verticalArrangement = Arrangement.SpaceBetween
) {
    Text(
        modifier = Modifier.fillMaxWidth()
            .background(color = Color.Yellow.copy(alpha = 0.5f)),
        text = "Top Text",
        style = MaterialTheme.typography.h2
    )
    Text(text = "Content", style = MaterialTheme.typography.h2)
    MyTextField("Text Field 1")
    MyTextField("Text Field 2")
    Text(
        modifier = Modifier.fillMaxWidth()
            .background(color = Color.Yellow.copy(alpha = 0.5f)),
        text = "Bottom Text",
        style = MaterialTheme.typography.h2
    )
}

When the keyboard pops up, the Bottom Text will also be pushed up, because imePadding acts on the layout of the whole block

If we change this way and only wrap the part of the input box, the keyboard will not top up the UI at the bottom:

    Column(
        modifier = Modifier.fillMaxSize().statusBarsPadding()
            .background(color = Color.Cyan.copy(alpha = 0.2f)),
        verticalArrangement = Arrangement.SpaceBetween
    ) {
        Text(
            modifier = Modifier.fillMaxWidth()
                .background(color = Color.Yellow.copy(alpha = 0.5f)),
            text = "Top Text",
            style = MaterialTheme.typography.h2
        )
        Text(text = "Content", style = MaterialTheme.typography.h2)

        Text(
            modifier = Modifier.fillMaxWidth()
                .background(color = Color.Yellow.copy(alpha = 0.5f)),
            text = "Bottom Text",
            style = MaterialTheme.typography.h2
        )
    }

The two effects are shown in the figure below:

Summary and extension of keyboard part

Summary: the processing of input box keyboard includes:

  • adjustResize.
  • Set reasonable bottom padding: where to set and how much to set
  • Let the View actively bring itself to the visible position

Insets library also provides examples of keyboard disappearing and appearing with scrolling If you are interested, you can have a look

Summary of the use of acompanist insets

The acompanist insets library helps us do two parts:

  • Obtain various insets information and provide it with CompositionLocalProvider
  • Inside the Provider, the directly available modifier or size can be obtained through the modifier

However, there are some points that need attention when using this library, such as:

  • If you forget to set windowcompat Setdecorfitssystemwindows (window, false), the obtained values are 0
  • Parameter of providedwindowinsets: consumewindowninsets is true by default. It is recommended to set it to false to facilitate the inner ui to continue to use the values of these insets
@Composable
fun ProvideWindowInsets(
    consumeWindowInsets: Boolean = true,
    windowInsetsAnimationsEnabled: Boolean = true,
    content: @Composable () -> Unit
)
  • If providedwindowinsets are nested in the layout, it may not work as expected (I don't know if it is a temporary issue)

References

Keywords: Android compose

Added by dietkinnie on Thu, 03 Feb 2022 03:44:49 +0200