Hands-on with Material Components for Android: Bottom Navigation

Part 2 of a series covering practical usage of Material Components for Android

Nick Rout
Over Engineering
Published in
7 min readMar 7, 2019

--

Featured in Android Weekly Issue #353

This post will be covering the features and API of the Bottom Navigation component. To find out how to handle initial setup of Material Components for Android (including the Gradle dependency and creating an app theme), please see my original post:

The Bottom Navigation bar is a top-level navigation component. It displays three to five destinations, each with an icon and an optional text label. It is an ergonomic component; its bottom placement making it easy to reach with a single hand on mobile devices.

The characteristics of these destinations are:

  • They should be of equal importance in the context of your app
  • They should be accessible from anywhere in the app (meaning the Bottom Navigation bar remains visible even when navigating downward within the current task hierarchy)
  • They should not represent once-off actions that start a new task (eg. Composing an email)
  • They should not represent user preferences or settings

Note: It is recommended to only use Bottom Navigation for mobile and tablet devices. For other form factors, consider different navigation components such as the Navigation Drawer. For more information, refer to the Understanding navigation article.

Basic usage 🏁

A BottomNavigatonView can be included in your screen layout like so:

<FrameLayout
...
>

<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNavigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
/>

</FrameLayout>

Handling navigation items 🧭

The navigation destinations of a BottomNavigationView are added by inflating a menu. This can be done in XML:

<com.google.android.material.bottomnavigation.BottomNavigationView
...
app:menu="@menu/menu_bottom_navigation"
/>

Alternatively, it can be done programmatically:

bottomNavigation.inflateMenu(R.menu.menu_bottom_navigation)

Note: Attempting to inflate a menu with more than 5 items will crash with an IllegalStateException. To dynamically determine the max number of items, use BottomNavigationView#maxItemCount.

Detecting when navigation items have been selected can be done with a convenience function:

bottomNavigation.setOnNavigationItemSelectedListener { item ->
when
(item.itemId) {
R.id.item1 -> {
// Do something for navigation item 1
true
}
R.id.item2 -> {
// Do something for navigation item 2
true
}
else -> false
}
}

There also exists a function for detecting when navigation items have been reselected:

bottomNavigation.setOnNavigationItemReselectedListener { item ->
when
(item.itemId) {
R.id.item1 -> {
// Do something for navigation item 1
}
R.id.item2 -> {
// Do something for navigation item 2
}
}
}

Lastly, navigation items can be programmatically selected in the following way:

bottomNavigation.selectedItemId = R.id.item1

Adjusting item appearance and behavior ✅

The appearance and position of navigation items can be adjusted, depending on number of items, selected state and design preferences. Specifically, this consists of item label visibility and horizontal translation.

Label visibility

The labelVisibilityMode attribute can be used to adjust the behavior of the text labels for each navigation item. There are four visibility modes:

  • LABEL_VISIBILITY_AUTO: The label behaves as “labeled” when there are 3 items or less, or “selected” when there are 4 items or more (this is the default behavior)
  • LABEL_VISIBILITY_SELECTED: The label is only shown on the selected navigation item
  • LABEL_VISIBILITY_LABELED: The label is shown on all navigation items
  • LABEL_VISIBILITY_UNLABELED: The label is hidden for all navigation items

Changing the mode can be done in XML:

<com.google.android.material.bottomnavigation.BottomNavigationView
...
app:labelVisibilityMode="selected"
/>

Alternatively, it can be done programmatically:

bottomNavigation.labelVisibilityMode = LabelVisibilityMode.LABEL_VISIBILITY_SELECTED

Horizontal translation

The itemHorizontalTranslationEnabled attribute can be used to set whether or not navigation items should “shift” when selected/deselected. The default value is false. The source code reveals that this behavior also depends on the chosen labelVisibilityMode and the amount of items. In order for shifting to occur, the following requirements also need to be met:

  • labelVisibilityMode = LABEL_VISIBILITY_AUTO and item count > 3 or
  • labelVisibilityMode = LABEL_VISIBILITY_SELECTED

Even with all of the above satisfied, the combined widths of the item child views needs to fill the screen width in order for this to occur. In practical terms, this seems to equate to a high item count (4 or more) when a mobile device is used in portrait orientation. Phew!

Item horizontal translation enabled/disabled

Changing this flag can be done in XML:

<com.google.android.material.bottomnavigation.BottomNavigationView
...
app:itemHorizontalTranslationEnabled="true"
/>

Alternatively, it can be done programmatically:

bottomNavigation.isItemHorizontalTranslationEnabled = true

Badging 🔢

Navigation items can be badged to indicate an important update to a particular destination, such as a push notification or new message. Badges appear as a dot (with an optional number) displayed over the item icon in the top right corner. This is achieved with a relatively simple API exposed by BottomNavigationView:

bottomNavigation.getOrCreateBadge(R.id.item1) // Show badge
bottomNavigation.removeBadge(R.id.item1) // Remove badge
val badge = bottomNavigation.getBadge(R.id.item1) // Get badge
A basic badge

Both BottomNavigationView#getBadge (nullable) and BottomNavigationView#getOrCreateBadge (non-null) return the badge as an instance of the BadgeDrawable class. This class exposes its own API for more advanced customization options:

  • setNumber/getNumber/hasNumber/clearBadgeNumber: Used to assign, retrieve, check and clear a number displayed inside the badge. A badge is displayed without a number by default.
A badge with a number
  • setMaxCharacterCount/getMaxCharacterCount: Used to set/get the maximum number of characters allowed in a badge number before it is truncated with a ‘+’. The default value is 4.
A badge with a high (truncated) number
  • setBadgeGravity/getBadgeGravity: Used to set/get the gravity of the badge which can be TOP_END, TOP_START, BOTTOM_END or BOTTOM_START. The default value is TOP_END.
A badge with gravity set to bottom start
  • setHorizontalOffset/getHorizontalOffset and setVerticalOffset/setVerticalOffset: Used to set/get the offset of the badge towards the center of its anchor.

Tooltips ℹ️

A tooltip will be shown above a navigation item when it is long pressed or on a hover event (when using input devices such as a mouse). This defaults to the title of the menu item. You can override this behavior with custom tooltip text in the following way:

<menu
...
>

<item
android:id="@+id/item1"
android:title="Item 1"
app:tooltipText="Tooltip text"
/>
...

</menu>

Theming 🎨

BottomNavigationView can be themed in terms of the three Material Theming subsystems: color, typography and shape. There are two style variants that inherit from Widget.MaterialComponents.BottomNavigationView, each with an optional style suffix: surface (default, no suffix) and colored (*.Colored). When implementing a global custom BottomNavigationView style, reference it in your app theme with the bottomNavigationStyle attribute.

Badges can also be themed. There is a single existing style; Widget.MaterialComponents.Badge. When implementing a global custom badge style, reference it in your app theme with the badgeStyle attribute.

Color

The color of the BottomNavigationView background can be customized with the backgroundTint attribute. This defaults to colorSurface for surface Bottom Navigation and colorPrimary for colored Bottom Navigation.

The color of the BottomNavigationView navigation item icons/labels can be customized with the itemIconTint/itemTextColor attributes respectively. Typically you would want to keep these the same. These require a ColorStateList, meaning a <selector> for checked/enabled/disabled states is required. They default to colorOnSurface(unchecked)/colorPrimary(checked) for surface Bottom Navigation and colorOnPrimary for colored Bottom Navigation, with different opacities per state (which you can find in the documentation).

Lastly, the color of the BottomNavigationView navigation item touch ripples can be customized with the itemRippleColor attribute. It too accepts a ColorStateList and the default values are the same as itemIconTint/itemTextColor.

Color theming

Badge colors can also be customized with the backgroundColor and badgeTextColor attributes. By default, these are colorError and colorOnError respectively. These can also be applied programmatically to a BadgeDrawable.

Badge color theming

Typography

The text labels of the BottomNavigationView items will adopt the fontFamily attribute defined in your app theme.

The other type aspects of these labels can be customized with the itemTextAppearanceActive/itemTextAppearanceInactive attributes, for checked/unchecked states respectively. Typically you would want to keep these the same. They default to textAppearanceCaption for all Bottom Navigation styles.

Type theming

Despite the existence of a TextAppearance.MaterialComponents.Badge style, no theme attributes currently exist in order to customize this.

Shape

There are no aspects of a BottomNavigationView that can be adjusted with shape theming, as the background shape spans the width of the screen.

While not strictly shape theming, it is worth mentioning that the size of navigation item icons can be adjusted with BottomNavigationView#itemIconSize.

More resources 📚

I hope this post has provided some insight into Bottom Navigation and how it can be used in your Android app(s). If you have any questions, thoughts or suggestions then I’d love to hear from you!

Find me on Twitter @ricknout

--

--

Nick Rout
Over Engineering

Principal Android Engineer at GoDaddy | Ex-Google | Google Developer Expert for Android