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 itemLABEL_VISIBILITY_LABELED
: The label is shown on all navigation itemsLABEL_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 orlabelVisibilityMode
=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!
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
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.
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.
setBadgeGravity
/getBadgeGravity
: Used to set/get the gravity of the badge which can beTOP_END
,TOP_START
,BOTTOM_END
orBOTTOM_START
. The default value isTOP_END
.
setHorizontalOffset
/getHorizontalOffset
andsetVerticalOffset
/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
.
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
.
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.
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 📚
- The source code for the Playground app used in this article can be found on GitHub.
- Bottom Navigation Design Documentation
- Bottom Navigation API Documentation
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