Hands-on with Material Components for Android: Bottom Navigation

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

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

Theming 🎨

The 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.

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

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

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