Bottom navigation was introduced in Google’s Material Design Guidelines, it was added in Android’s design support library version 25.
To get the BottomNavigationView
component, first we need to include the material components library with AndroidX enabled.
In your build.gradle file, add the following:
dependencies { implementation 'com.google.android.material:material:1.2.0-alpha02' }
You can find last version here.
First, let’s define sample menu items that we’ll put inside our BottomNavigationView
. We’ll create three sample items: "Item 1" and "Item 2":
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/item1" android:icon="@android:drawable/ic_menu_add" android:title="@string/item1" /> <item android:id="@+id/item2" android:icon="@android:drawable/ic_menu_call" android:title="@string/item1" /> </menu>
Now let’s create a simple layout containing a ViewPager and a BottomNavigationView below it:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <androidx.viewpager.widget.ViewPager android:id="@+id/view_pager" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"/> <com.google.android.material.bottomnavigation.BottomNavigationView android:id="@+id/bottom_navigation_view" android:layout_width="match_parent" android:layout_height="wrap_content" app:elevation="16dp" app:itemRippleColor="@color/colorPrimary" app:menu="@menu/bottom"/> </LinearLayout>
We will create three fragment classes.
class ItemFragment : Fragment() { private var title: String = "" private val EXTRA_TITLE = "TITLE" companion object{ fun newInstance(title: String) = ItemFragment().apply{ arguments = Bundle(1).apply { putString(EXTRA_TITLE, title) } } } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_item, container, false) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) arguments?.let { title = it.getString(EXTRA_TITLE) } tvTitle.text = title } }
Now we define the MainPagerAdapter we’ll use to populate the ViewPager:
class MainPagerAdapter(fm: FragmentManager) : FragmentPagerAdapter(fm, FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { private val screens = arrayListOf<Fragment>() fun setItems(screens: List<Fragment>) { this.screens.apply { clear() addAll(screens) notifyDataSetChanged() } } fun getItems(): List<Fragment> { return screens } override fun getItem(position: Int): Fragment { return screens[position] } override fun getCount(): Int { return screens.size } }
We’re now ready to go, so let’s set it all up in the MainActivity. As a bonus, we’ll also show the title of the displayed screen in the toolbar:
class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemSelectedListener { private lateinit var mainPagerAdapter: MainPagerAdapter private val titles = arrayListOf("Screen 1", "Screen 2") private val ids = arrayListOf(R.id.item1, R.id.item2) private val screens = arrayListOf( ItemFragment.newInstance(titles[0]), ItemFragment.newInstance(titles[1]) ) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) mainPagerAdapter = MainPagerAdapter(supportFragmentManager) mainPagerAdapter.setItems(screens) scrollToScreen(screens.first()) selectBottomNavigationViewMenuItem(R.id.item1) supportActionBar?.setTitle(titles.first()) bnvItems.setOnNavigationItemSelectedListener(this) vpItems.adapter = mainPagerAdapter vpItems.addOnPageChangeListener(object : ViewPager.SimpleOnPageChangeListener() { override fun onPageSelected(position: Int) { selectBottomNavigationViewMenuItem(ids[position]) supportActionBar?.setTitle(titles[position]) } }) } private fun scrollToScreen(screen: Fragment) { val screenPosition = mainPagerAdapter.getItems().indexOf(screen) if (screenPosition != vpItems.currentItem) { vpItems.currentItem = screenPosition } } private fun selectBottomNavigationViewMenuItem(menuItemId: Int) { bnvItems.setOnNavigationItemSelectedListener(null) bnvItems.selectedItemId = menuItemId bnvItems.setOnNavigationItemSelectedListener(this) } override fun onNavigationItemSelected(menuItem: MenuItem): Boolean { when(menuItem.itemId) { R.id.item1 -> { scrollToScreen(screens[0]) supportActionBar?.setTitle(titles[0]) return true } R.id.item2 -> { scrollToScreen(screens[1]) supportActionBar?.setTitle(titles[1]) return true } else -> return false } } }
Example of item selecting
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 } }
Badges
The Bottom Navigation View items can include badges in their right upper corner as a notification. The badges contain dynamic information such as a number of pending requests and a colorful dot. Without further ado let’s create a badge notification on navigation item.
val badgeDrawable : BadgeDrawable = bottomNav.showBadge(R.id.item1)
A badge displayed without a number is the default behavior. Now let’s say I want to show a notification count 1, for the clock menu item. The BadgeDrawable class exposes the convenience method to add notification count via the setNumber method.
bottomNav.showBadge(R.id.item1).apply { number = 1 }
Notification must be removed or dismissed once we tap on the item. We can do this by the setOnNavigationItemSelectedListener
method to detect whether the item showing badge notification then simply removed it.
bottomNav.setOnNavigationItemSelectedListener { item: MenuItem -> val itemId = item.itemId bottomNav.getBadge(itemId)?.let { badgeDrawable -> if (badgeDrawable.isVisible) // check whether the item showing badge bottomNav.removeBadge(itemId) // remove badge notification } return@setOnNavigationItemSelectedListener true }