Android Design Development

Modifying Android's Navigation Bar for a More Immersive Experience

March 20, 2018

A major­i­ty of Android devices have a soft­ware nav­i­ga­tion bar which (for every oth­er app I can think of) is a sol­id black (or white) bar that takes up 48dp of your ver­ti­cal screen space. On the Google Pix­el this equates to some­where between 7 – 10% of the total ver­ti­cal screen space (depend­ing on your acces­si­bil­i­ty set­tings for screen size). Con­sid­er­ing that most mobile apps are opti­mized for show­ing you a list of things in a win­dow that scrolls ver­ti­cal­ly, we might want to reclaim some of this space for the con­tent of our apps. This will make your app feel more immer­sive by using more of the screen to dis­play your con­tent. You could cer­tain­ly hide the nav­i­ga­tion UI alto­geth­er, but this makes it more dif­fi­cult for the user to nav­i­gate between screens in your app because they will have to swipe up from the bot­tom of the screen to reveal the hid­den nav­i­ga­tion bar. I stum­bled on a bet­ter answer by acci­dent when I noticed that the news feed sec­tion of the Pix­el launch­er has a semi-trans­par­ent bot­tom nav­i­ga­tion bar:

Google Feed

As it turns out, this translu­cent navbar option has been around since KitKat (API Lev­el 19)! All you have to do is set android:windowTranslucentNavigation to true and it just works, right?

The Part Where it Gets Com­pli­cat­ed #

Nope, sor­ry, I lied. Even though we’ve request­ed that the app be shown with a translu­cent nav­i­ga­tion bar, the app’s win­dow still is sized to the area between the nav­i­ga­tion bar and the sta­tus­bar. For­tu­nate­ly, the KitKat release notes help­ful­ly explain that in order to get your app to dis­play con­tent under the nav­i­ga­tion bar, you must also set android:fitsSystemWindows to false. This allows the Activity’s win­dow to be drawn at the full size of the device’s screen.

By this point, you’ve prob­a­bly won­dered How do I tap on items that are dis­played under­neath the nav­i­ga­tion bar?” The answer is You can’t” because the nav­i­ga­tion bar does not pass touch events through to your appli­ca­tion. There­fore, we have to add padding to the bot­tom of the app’s con­tent so that it can be scrolled into an acces­si­ble posi­tion above the nav­i­ga­tion bar. The amount of padding required is sim­ply the height of the navbar which be deter­mined by retriev­ing the val­ue of android.R.dimen.navigation_bar_height from the sys­tem resources:

val Resources.navBarHeight: Int @Px get() {
  val id = getIdentifier("navigation_bar_height", "dimen", "android")
  return when {
    id > 0 -> getDimensionPixelSize(id)
    else -> 0
  }
}

Then apply that many pix­els of padding to the bot­tom of your layout’s scrolling ele­ment (in this exam­ple, a RecyclerView):

recyclerView.setPaddingRelative(
  recyclerView.paddingStart,
  recyclerView.paddingTop,
  recyclerView.paddingEnd,
  recyclerView.paddingBottom + resources.navBarHeight
)

You will also need to set android:clipToPadding to false on your RecyclerView or ScrollView oth­er­wise Android will not draw any child views inside the padding area.

What About Hard­ware Nav­i­ga­tion But­tons? #

Phones such as the Sam­sung Galaxy S7 use phys­i­cal but­tons for the Back/​Home/​Switcher actions and do not show the soft­ware nav­i­ga­tion bar on the screen. Some of these even let you choose between the hard­ware but­tons and the onscreen but­tons. For­tu­nate­ly, we can read android.R.bool.config_showNavigationBar to deter­mine if the soft­ware navbar is being shown:

val Resources.showsSoftwareNavBar: Boolean get() {
  val id = getIdentifier("config_showNavigationBar", "bool", "android")
  return id > 0 && getBoolean(id)
}

// Inset bottom of content if drawing under the translucent navbar, but
// only if the navbar is a software bar
if (resources.showsSoftwareNavBar) {
  recyclerView.setPaddingRelative(/* ... */)
}

Every­thing Goes Side­ways #

Rota­tion con­fig­u­ra­tion changes have always been tricky for Android apps to han­dle and this is no dif­fer­ent. When you rotate a phone into the 90° or 270° ori­en­ta­tion, the nav­i­ga­tion bar remains on the side of the phone that is the bot­tom” in the 0° ori­en­ta­tion. In these ori­en­ta­tions, dis­play­ing con­tent under the navbar los­es its use­ful­ness, so we’ll dis­able it. The eas­i­est way to do this is by over­rid­ing these attrib­ut­es of the activity’s theme based on orientation:

<!-- values/styles.xml -->
<bool name="fullscreen_style_fit_system_windows">false</bool>
<bool name="fullscreen_style_use_translucent_nav">true</bool>
<style name="AppTheme.Fullscreen">
    <item name="android:fitsSystemWindows">
        @bool/fullscreen_style_fit_system_windows
    </item>
    <item name="android:windowTranslucentNavigation">
        @bool/fullscreen_style_use_translucent_nav
    </item>
</style>

<!-- values-land/styles.xml -->
<bool name="fullscreen_style_fit_system_windows">true</bool>
<bool name="fullscreen_style_use_translucent_nav">false</bool>

We will also have to add the padding to the scrolling con­tent of the view conditionally:

inline val Resources.isNavBarAtBottom: Boolean get() {
  // Navbar is always on the bottom of the screen in portrait mode, but rotates
  // with device in landscape orientations
  return this.configuration.orientation == ORIENTATION_PORTRAIT
}

// Inset bottom of content if drawing under the translucent navbar, but
// only if the navbar is a software bar and is on the bottom of the screen.
if (resources.showsSoftwareNavBar && resources.isNavBarAtBottom) {
  recyclerView.setPaddingRelative(/* ... */)
}

But wait! What about tablets? Tablets do rotate the navbar to the bot­tom of the screen in all ori­en­ta­tions. There is no isTablet prop­er­ty pro­vid­ed by Android since the def­i­n­i­tion of tablet” is rather ambigu­ous in the pres­ence of large phablet” phones, so we have to cre­ate our own. Android treats devices with a screen whose small­est dimen­sion is at least 600dp dif­fer­ent­ly, most impor­tant­ly for our use case, let­ting the nav­i­ga­tion bar rotate. We can again use resource splits to cre­ate dif­fer­ent val­ues for isTablet depend­ing on the device’s screen that we can check in our code:

<!-- values/bools.xml -->
<bool name="is_tablet">false</bool>

<!-- values-sw600dp/bools.xml -->
<bool name="is_tablet">true</bool>
inline val Resources.isTablet: Boolean get() = getBoolean(R.bool.is_tablet)

inline val Resources.isNavBarAtBottom: Boolean get() {
  // Navbar is always on the bottom of the screen in portrait mode, but may
  // rotate with device if its category is sw600dp or above.
  return this.isTablet || this.configuration.orientation == ORIENTATION_PORTRAIT
}

Catch 24 #

Android 7.0 Nougat (API 24) intro­duced yet anoth­er ele­ment for us to wor­ry about: Mut­li-Win­dow mode. In Mul­ti-Win­dow mode, two activ­i­ties may be shown side-by-side or stacked ver­ti­cal­ly depend­ing on device ori­en­ta­tion and nei­ther app gets to draw under the nav­i­ga­tion bar. Since we can­not per­form any of our fan­cy nav­i­ga­tion bar styling in these cas­es, we will dis­able our mod­i­fi­ca­tions to the activ­i­ty theme and padding applied to the con­tent in Mul­ti-Win­dow mode:

inline val Activity.isInMultiWindow: Boolean get() {
  return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    isInMultiWindowMode
  } else {
    false
  }
}

// Apps can't draw under the navbar in multiwindow mode.
val fitSystemWindows = if (activity?.isInMultiWindow == true) {
  true
} else {
  resources.getBoolean(R.bool.fullscreen_style_fit_system_windows)
}
// Override the activity's theme when in multiwindow mode.
coordinatorLayout.fitsSystemWindows = fitSystemWindows

if (!fitSystemWindows) {
  // Inset bottom of content if drawing under the translucent navbar, but
  // only if the navbar is a software bar and is on the bottom of the screen.
  if (resources.showsSoftwareNavBar && resources.isNavBarAtBottom) {
    recyclerView.setPaddingRelative(/* ... */)
  }
}

One More Thing #

With fitsSystemWindows="false", the Activ­i­ty is also drawn under­neath the sta­tus­bar, which we have com­plete­ly ignored so far. For­tu­nate­ly, its behav­ior is much more pre­dictable than the nav­i­ga­tion bar since it is always shown on all phones (except when explic­it­ly hid­den by the activ­i­ty theme, which we did not do), and it is always shown at the top of the screen. Thus, whether we add padding to our AppBarLayout or not will fol­low the fitSystemWindows set­ting with no exceptions:

val Resources.statusBarHeight: Int @Px get() {
  val id = getIdentifier("status_bar_height", "dimen", "android")
  return when {
    id > 0 -> getDimensionPixelSize(id)
    else -> 0
  }
}

if (!fitSystemWindows) {
  // ...
  // Inset the toolbar when it is drawn under the status bar.
  barLayout.updatePaddingRelative(
    barLayout.paddingStart,
    barLayout.paddingTop + resources.statusBarHeight,
    barLayout.paddingEnd,
    barLayout.paddingBottom
  )
}

A Piece of Everyone’s Least Favorite Can­dy #

Right at the begin­ning, I said that KitKat was the Android ver­sion that enabled use of translu­cent nav­i­ga­tion bars, but like so many things in KitKat, you can’t get it to work prop­er­ly. The config_showNavigationBar val­ue that we check to see if we need to inset the scrolling con­tent always returns false when fitsSystemWindows is false. The navigation_bar_height dimen­sion also always returns 0 in this case, mak­ing it impos­si­ble to mea­sure how much padding we should add to the con­tent. To imple­ment our solu­tion on KitKat we have to assume the device always has a soft­ware nav­i­ga­tion bar (which is less and less like­ly the old­er the device is), esti­mate the padding required (almost always 48dp, 56dp for tablets meet­ing the sw900dp qual­i­fi­er), and just deal with excess space at the bot­tom for devices that don’t. That’s too many con­di­tions to leave to chance (espe­cial­ly for KitKat), so I’ve opt­ed to dis­able the translu­cent navbar using anoth­er resource split:

<!-- values/styles.xml -->
<bool name="fullscreen_style_fit_system_windows">true</bool>
<bool name="fullscreen_style_use_translucent_nav">true</bool>
<style name="AppTheme.Fullscreen">
  <!-- KitKat can show a translucent navbar, but config_showNavigationBar
    is always false so you can't really tell if the device has hardware nav
    keys or not. -->
  <item name="android:fitsSystemWindows">
    @bool/fullscreen_style_fit_system_windows
  </item>
</style>

<!-- values-v21/styles.xml -->
<bool name="fullscreen_style_fit_system_windows">false</bool>
<style name="AppTheme.Fullscreen">
  <item name="android:fitsSystemWindows">
    @bool/fullscreen_style_fit_system_windows
  </item>
  <item name="android:windowTranslucentNavigation">
    @bool/fullscreen_style_use_translucent_nav
  </item>
</style>

Con­clu­sion #

There’s a lot of stuff to keep in mind here, so I’ve made a sam­ple app demon­strat­ing all these con­cepts avail­able on GitHub. If you’d like to play around with the app, it is also avail­able on the Google Play Store. It’s a very sim­ple exam­ple that shows the col­ors of the Mate­r­i­al palette in a grid:

Translucent Navbar

Stay in the loop with our latest content!

Select the topics you’re interested to receive our new relevant content in your inbox. Don’t worry, we won’t spam you.

Bringing Agile Home
Miscellaneous

Bringing Agile Home

August 26, 2020

My life often resembles a game of Whack-A-Mole. The moment I complete one task two more pop up. To avoid getting behind, I jump on new tasks almost immediately. Then at some point along the way my wife will innocently ask if I’ve scheduled my appointment with the Secretary of State. Realizing that an up-to-date driver’s license takes priority over a squeaky hinge, I put down my tools and book my online appointment.

Read more
Clutch Names Michigan Software Labs as a 2019 Top Developer in U.S.A.
Business Team

Clutch Names Michigan Software Labs as a 2019 Top Developer in U.S.A.

November 14, 2019

Clutch is a B2B research, ratings, and reviews firm located in downtown Washington, D.C.. Clutch connects businesses with the best-fit agencies, software, and consultants they need to tackle business challenges together and with confidence. Clutch’s methodology compares business service providers and software in a specific market based on verified client reviews, services offered, work quality, and market presences.

Read more
How to Build a Better App UI Architecture - Part 2
Development

How to Build a Better App UI Architecture - Part 2

November 19, 2019

Following up on the importance of consistency and reusability in Part 1, let's look at how to optimize the UI system for better consistency and reusability. We’ll begin by breaking down an iOS app, then look at how to build an interfaces-based system.

Read more
View more articles