It may not be obvious at first glance but this topic brings various nooks and crannies. There are many circumstances you need to be aware of while changing your system bar colors. So let's start with empty activity containing sample image.

At first, we need to enable drawing behind status bar and navigation bar by using setDecorFitsSystemWindows() method directly on window. However, this method is available only from API 30 so we will use setDecorFitsSystemWindows() method from WindowCompat instead. This method has been introduced in version 1.5.0-alpha02 of Core KTX for backward compatibility. We add this line to onCreate() method.

WindowCompat.setDecorFitsSystemWindows(window, false)

System Bar Color

Now when our window draws its content also behind status bar and navigation bar, we need to set their color to transparent so we are able to see image behind them. To change color of system bars we can use simple methods setStatusBarColor() and setNavigationBarColor() on window:

window.statusBarColor = Color.TRANSPARENT
window
.navigationBarColor = Color.TRANSPARENT

However, the navigation bar color is not transparent as we set. The reason is that Android from version 10 is ensuring that the navigation bar has enough contrast. So in this case we need to disable this ensuring with setNavigationBarContrastEnforced() on our window:

if (Build.VERSION_CODES.Q <= Build.VERSION.SDK_INT) {
window.isNavigationBarContrastEnforced = false
}

or directly in our v29 style:

<item name="android:enforceNavigationBarContrast">false</item>

Now system doesn't care about transparency of navigation bar and trusts us.

System Bar Icons Color

Because of transparent status bar and light icons, we cannot see them. While status bar and navigation bar can have any color as their background, the color of their icons is limited to light (default) and dark. Just to make sure, status bar with dark icons is called light status bar and navigation bar with dark icons is called light navigation bar. Light status bar is supported from Android 6 and light navigation bar is supported from Android 8.0.

To set dark icons, we need to add flag to window's insetsController. In case of status bar we add flag APPEARANCE_LIGHT_STATUS_BARS and in case of navigation bar we add flag APPEARANCE_LIGHT_NAVIGATION_BARS. But this scenario works on Android 11 and newer. On older versions we need to add flag on decorView's systemUiVisibility. If we are setting dark icons on status bar, we use flag SYSTEM_UI_FLAG_LIGHT_STATUS_BAR, in case of navigation bar we use flag SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.

The final extension functions could look like this:

Now we can set status bar icons color to dark to make them visible:

window.setStatusBarDarkIcons(true)

Special cases

Currently we are satisfied with our transparent system bars but let's imagine that content is scrollable and we have 3 images like this in a list. And there can be a moment when sky is behind navigation bar and the icons would therefore not be visible:

To be sure, we can set navigation bar color to semitransparent so we would be able to see its icons in any scroll position. Pretty simple but there is a case, when it is not required:

When gesture navigation is on, we don't have to set navigation bar color or color of its icons because navigation bar elements changes its color by content behind it. To check if gesture navigation is currently used, you can use this snippet:

Another useful computed property can be navigation bar height:

val Context.navigationBarHeight: Int
get() = resources.getDimensionPixelSize(
resources.getIdentifier(
"navigation_bar_height",
"dimen",
"android"
)
)

Its result can be used as bottom padding for your scrolling view to keep last element above navigation bar when you reach the end of list. Also, don't forget to set clipToPadding to false on your scrolling view.

To get status bar height you can use similar computed property:

val Context.statusBarHeight: Int
get() = resources.getDimensionPixelSize(
resources.getIdentifier(
"status_bar_height",
"dimen",
"android"
)
)

You can also change system bar colors using the animation:

fun Window.setStatusBarBackgroundColor(@ColorInt color: Int) {
ValueAnimator.ofObject(
ArgbEvaluator(),
statusBarColor,
color
).apply {
addUpdateListener {
statusBarColor = it.animatedValue as Int
}
}
.start()
}
fun Window.setNavigationBarBackgroundColor(@ColorInt color: Int) {
ValueAnimator.ofObject(
ArgbEvaluator(),
navigationBarColor,
color
).apply {
addUpdateListener {
navigationBarColor = it.animatedValue as Int
}
}
.start()
}

Conclusion

Some of these attributes can be safely change in styles.xml file. This solution is useful when you want to change system bar styles programmatically with more options. Especially if you adjust system bars dynamically per activity or per fragment based on screen content.

Thank you for reading and feel free to leave a comment!

Android Developer