WindowInsets
is the standard API in Jetpack Compose for handling
areas of the screen that are partially or fully obscured by the system UI. These
areas include the status bar, navigation bar, and on-screen keyboard. You can
alternatively pass predefined WindowInsetsRulers
like SafeDrawing
to
Modifier.fitInside
or Modifier.fitOutside
to align your content
with the system bars and the display cutout or create custom
WindowInsetsRulers
.
Advantages of WindowInsetsRulers
- Avoids Consumption Complexity: It operates during the placement phase
of layout. This means it completely bypasses the inset consumption chain and
can always provide the correct, absolute positions of system bars and display
cutouts, regardless of what parent layouts have done. Using the
Modifier.fitInside
orModifier.fitOutside
methods are helpful in fixing issues when ancestor Composables incorrectly consume insets. - Easily avoid the system bars: It helps your app content avoid system bars
and the display cutout, and can be more straightforward than using
WindowInsets
directly. - Highly customizable: Developers can align content to custom rulers, and have precise control over their layouts with custom layouts.
Disadvantages of WindowInsetsRulers
- Can't be used for Measurement: Because it operates during the placement phase, the positional information it provides is not available during the earlier measurement phase.
- Potential for Layout Instability: This can cause crashes if a parent
layout size depends on the size of its children. Since a child using
WindowInsetsRulers
might change its position or size during placement, it can create an unstable layout cycle.
Create custom WindowInsetsRulers
You can align content to custom rulers. For example, consider the use case where
a parent composable improperly handles insets causing padding issues in a
downstream child. While this issue can be solved in other ways, including by
using Modifier.fitInside
, you can also create a custom ruler to precisely
align the child composable without having the fix the issue in the parent
upstream as shown in the following example and video:
@Composable fun WindowInsetsRulersDemo(modifier: Modifier) { Box( contentAlignment = BottomCenter, modifier = modifier .fillMaxSize() // The mistake that causes issues downstream, as .padding doesn't consume insets. // While it's correct to instead use .windowInsetsPadding(WindowInsets.navigationBars), // assume it's difficult to identify this issue to see how WindowInsetsRulers can help. .padding(WindowInsets.navigationBars.asPaddingValues()) ) { TextField( value = "Demo IME Insets", onValueChange = {}, modifier = modifier // Use alignToSafeDrawing() instead of .imePadding() to precisely place this child // Composable without having to fix the parent upstream. .alignToSafeDrawing() // .imePadding() // .fillMaxWidth() ) } } fun Modifier.alignToSafeDrawing(): Modifier { return layout { measurable, constraints -> if (constraints.hasBoundedWidth && constraints.hasBoundedHeight) { val placeable = measurable.measure(constraints) val width = placeable.width val height = placeable.height layout(width, height) { val bottom = WindowInsetsRulers.SafeDrawing.current.bottom .current(0f).roundToInt() - height val right = WindowInsetsRulers.SafeDrawing.current.right .current(0f).roundToInt() val left = WindowInsetsRulers.SafeDrawing.current.left .current(0f).roundToInt() measurable.measure(Constraints.fixed(right - left, height)) .place(left, bottom) } } else { val placeable = measurable.measure(constraints) layout(placeable.width, placeable.height) { placeable.place(0, 0) } } } }
The following video shows an example of problematic IME inset consumption
caused by an upstream parent in the image on the left, and using custom rulers
to fix the issue on the right. Extra padding is shown underneath the TextField
Composable as the navigation bar padding wasn't consumed by the parent. The
child is placed in the correct location in the right image using a custom ruler
as seen in the preceding code sample.
Verify that parents are constrained
In order to safely use WindowInsetsRulers
, make sure the parent provides valid
constraints. Parents must have a defined size and can't depend on the size of a
child that uses WindowInsetsRulers
. Use fillMaxSize
or other size modifiers
on parent Composables.
Similarly, placing a composable that uses WindowInsetsRulers
inside a
scrolling container like verticalScroll
can cause unexpected behavior as the
scrolling container provides unbounded height constraints, which are
incompatible with the ruler's logic.