Blog Infos
Author
Published
Topics
, , ,
Author
Published

In Android, the screen typically consists of both the app content and system-drawn components, including the top status bar and the bottom navigation bar. By default, these bars are fully managed and rendered by the system, which automatically sets their backgrounds according to the current theme, leaving the remaining area for the app to draw its content.

In many cases, we want the application’s drawing area to extend into the System UI or dynamically set its colors to achieve a better user experience.

Years ago, implementing an immersive status bar required setting a bunch of flags, XML configurations, etc. However, times have changed, and AndroidX now provides more convenient tools. This article will introduce how to achieve an immersive effect using Jetpack Compose.

To implement an immersive UI, we need to follow two steps:

  1. Extend the application’s drawing area into the System UI portion.
  2. Set the background color of the System UI and the margins of the page as needed.
enableEdgeToEdge()

By default, the background of the System UI area is drawn by the system with color blocks, as shown in the figure below.

To address this issue, you need to call the enableEdgeToEdge() function within Activity.onCreate. This function extends the application’s layout behind the System UI, allowing us to control the drawing of the System UI background. In most cases, we prefer a solid color block, but sometimes we might want it to be transparent.

At this point, the page will look like the image below, showing that the application’s drawing area has extended into the System UI portion.

Setting the System UI Background and Margins After completing the above steps, we still need to address another issue: the page margins. At this point, the content of the application might be obscured by the System UI, so we need to add appropriate margins to the page.

WindowInsets

WindowInsets is used to represent the position and size of the System UI.

@Stable
interface WindowInsets {
    /**
     * The space, in pixels, at the left of the window that the inset represents.
     */
    fun getLeft(density: Density, layoutDirection: LayoutDirection): Int

    /**
     * The space, in pixels, at the top of the window that the inset represents.
     */
    fun getTop(density: Density): Int

    /**
     * The space, in pixels, at the right of the window that the inset represents.
     */
    fun getRight(density: Density, layoutDirection: LayoutDirection): Int

    /**
     * The space, in pixels, at the bottom of the window that the inset represents.
     */
    fun getBottom(density: Density): Int

    companion object
}

We set the page margins by obtaining the WindowInsets object corresponding to the System UI.

WindowInsets contains various types, each corresponding to different types of System UI.

// The insets describing the status bars. These are the top system UI bars containing notification icons and other indicators.
WindowInsets.statusBars

// The status bar insets for when they are visible. If the status bars are currently hidden (due to entering immersive full screen mode), then the main status bar insets will be empty, but these insets will be non-empty.
WindowInsets.statusBarsIgnoringVisibility

// The insets describing the navigation bars. These are the system UI bars on the left, right, or bottom side of the device, describing the taskbar or navigation icons. These can change at runtime based on the user's preferred navigation method and interacting with the taskbar.
WindowInsets.navigationBars

// The navigation bar insets for when they are visible. If the navigation bars are currently hidden (due to entering immersive full screen mode), then the main navigation bar insets will be empty, but these insets will be non-empty.
WindowInsets.navigationBarsIgnoringVisibility

// ... and more ...
Page Padding

In the example above, our page currently extends into the System UI, causing part of the content to be obscured.

Now, we can obtain the padding through WindowInsets and apply it.

val density = LocalDensity.current
val statusBarHeight = WindowInsets.statusBars.getTop(density).pxToDp(density)
val navigatorBarHeight = WindowInsets.navigationBars.getBottom(density).pxToDp(density)

Box(
    modifier = Modifier
        .fillMaxSize()
        .padding(top = statusBarHeight, bottom = navigatorBarHeight),
) 

Then, the page will look like this:

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

Jobs

Doesn’t it look much better now?

Adaptive Padding in Composables

However, setting up each page like this seems cumbersome, doesn’t it? Fortunately, Compose provides many handy tools to assist developers in setting up margins.

For example, in the above example, you can also do this:

Box(
    modifier = Modifier
        .fillMaxSize()
        .statusBarsPadding()
        .navigationBarsPadding(),
)

// or Modifier.systemBarsPadding()
// or Modifier.safeDrawingPadding()

The above code ultimately prevents content from being obscured by adding padding.

But it’s still a bit cumbersome. Do we have to write like this for every page? Of course not. Compose’s Scaffold also helps us solve this problem.

When using the Material Design 3’s Scaffold, correctly using innerPadding helps us automatically add page margins.

Scaffold { innerPadding ->
    Box(
        modifier = Modifier
            .fillMaxSize()
            .padding(innerPadding),
    ) {
        // ...
    }
}

Here, innerPadding already includes the padding of the status bar and navigation bar, so you can apply it directly.

Furthermore, the behavior of Scaffold can be changed. If our page doesn’t need to add margins (such as an image viewer or video player), it’s better not to use Scaffold‘s innerPadding, but to control it through parameters.

@Composable
fun Scaffold(
    modifier: Modifier = Modifier,
    topBar: @Composable () -> Unit = {},
    bottomBar: @Composable () -> Unit = {},
    snackbarHost: @Composable () -> Unit = {},
    floatingActionButton: @Composable () -> Unit = {},
    floatingActionButtonPosition: FabPosition = FabPosition.End,
    containerColor: Color = MaterialTheme.colorScheme.background,
    contentColor: Color = contentColorFor(containerColor),
    contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
    content: @Composable (PaddingValues) -> Unit
)

As you can see, the Scaffold parameter includes a contentWindowInsets parameter, which by default adds the System UI’s WindowInsets. If you want to change this behavior, you can set an empty WindowInsets.

TopAppBar

When using Scaffold, the adaptive margin is achieved by applying innerPadding. However, Scaffold‘s topBar does not have innerPadding, but we still don’t need to manually set its padding. This is because, as a special component like TopAppBar, its internals also automatically set the padding.

@Composable
fun TopAppBar(
    title: @Composable () -> Unit,
    modifier: Modifier = Modifier,
    navigationIcon: @Composable () -> Unit = {},
    actions: @Composable RowScope.() -> Unit = {},
    windowInsets: WindowInsets = TopAppBarDefaults.windowInsets,
    colors: TopAppBarColors = TopAppBarDefaults.topAppBarColors(),
    scrollBehavior: TopAppBarScrollBehavior? = null
)

The windowInsets parameter in the above parameters is used to set the top padding. We can also change this behavior through parameters.

NavigationBar

As the NavigationBar at the bottom of the page, it also supports adaptive page padding.

fun NavigationBar(
    modifier: Modifier = Modifier,
    containerColor: Color = NavigationBarDefaults.containerColor,
    contentColor: Color = MaterialTheme.colorScheme.contentColorFor(containerColor),
    tonalElevation: Dp = NavigationBarDefaults.Elevation,
    windowInsets: WindowInsets = NavigationBarDefaults.windowInsets,
    content: @Composable RowScope.() -> Unit
)
StatusBar and NavigationBar Background Colors

In fact, after the above steps are set, the background color is easy to set, because this is all part of the page content. After all, it’s your code, so you can control it however you want.

For Compose, this is automatically set, too. If our page uses Scaffold, then Scaffold itself has a background color, which is the backgroundColor in Material Design.

@Composable
fun Scaffold(
    modifier: Modifier = Modifier,
    topBar: @Composable () -> Unit = {},
    bottomBar: @Composable () -> Unit = {},
    snackbarHost: @Composable () -> Unit = {},
    floatingActionButton: @Composable () -> Unit = {},
    floatingActionButtonPosition: FabPosition = FabPosition.End,
    containerColor: Color = MaterialTheme.colorScheme.background,
    contentColor: Color = contentColorFor(containerColor),
    contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
    content: @Composable (PaddingValues) -> Unit
)

The containerColor in the above code is the background color of the page. And because Scaffold adds padding to the top and bottom by default, the background color of StatusBar/NavigationBar is naturally the background color of the page.

If you use TopAppBar, it will also automatically set the color, and it will change according to the scrolling state. So if the page has a top bar, it’s best to use the official one because it looks much nicer, and it also saves us a lot of trouble.

Conclusion

In short, setting up an immersive status bar according to the latest specifications will be very convenient, and many components in Compose help us save a lot of work. Even if you encounter some special cases, there are concise ways to deal with them.

Also, feel free to follow me. I’ll continue to produce various types of original content.

Reference Documentation:

https://developer.android.com/develop/ui/views/layout/edge-to-edge

https://developer.android.google.cn/develop/ui/compose/layouts/insets

This article is previously published on proandroiddev.com.

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
In this part of our series on introducing Jetpack Compose into an existing project,…
READ MORE
blog
In the world of Jetpack Compose, where designing reusable and customizable UI components is…
READ MORE
blog
Hi, today I come to you with a quick tip on how to update…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu