Android: Bottom navigation bar. Listener, badges and Navigation UI.

logo android

The bottom navigation bar is one of the essential navigation components defined by Material Design. You see it every day in popular apps such as Spotify, Instagram, TikTok or YouTube. It displays several icons, sometimes with a text label, that provide access to the main sections of an app.

bottomnavigationview examples


>> Read this post in Spanish here <<


It’s therefore a component that every Android developer should know. In this tutorial, you’ll build a bottom navigation bar with the most common features. You’ll use the official Google implementation, named BottomNavigationView, and included in the Material Components for Android library.


Note. Google advises that a bottom navigation bar should contain at most five elements. Do you need more? Create a Navigation drawer menu:

And if wish to display actions, BottomAppBar is the droid you’re looking for.

Contents

  1. Sample project with BottomNavigationView
  2. Implementing navigation with OnNavigationItemSelectedListener
    1. Screen with fragment
    2. The listener
    3. Managing screen orientation changes
  3. Indicators with BadgeDrawable
  4. Show \ hide the bottom bar on scroll
    1. Creating a recycler view
    2. The magic of CoordinatorLayout
  5. Navigation with Navigation framework
  6. Action bar integration
  7. Source code

Sample project with BottomNavigationView

The current version of the sample project uses the latest stable versions available as of August 2023:

  • Android API 33
  • Gradle 8.1.1
  • Java 17
  • Android Studio Giraffe
  • Material Components 1.9

The language is Java. We’ll see little code and simple, so you’ll have no problem adapting it to Kotlin if you prefer this language.

This is the build.properties file:

apply plugin: 'com.android.application'

android {

    defaultConfig {
        applicationId "com.danielme.android.bottomnavigation"
        compileSdk 33
        minSdkVersion 26
        targetSdkVersion 33
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        coreLibraryDesugaringEnabled true
        sourceCompatibility JavaVersion.VERSION_17
        targetCompatibility JavaVersion.VERSION_17
    }
    namespace 'com.danielme.android.bottomnavigation'
}


dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.google.android.material:material:1.9.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
}

The project, simple but realistic, has a single activity. First, let’s see its layout. It contains a Toolbar at the top and a BottomNavigationView at the bottom. Both components are located inside a ConstraintLayout:

<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">
 
    <com.google.android.material.appbar.MaterialToolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
 
    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottom_navigation"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:menu="@menu/bottom_nav_menu" />
 
</androidx.constraintlayout.widget.ConstraintLayout>

Line 24 sets up the navigation menu. How do we create it? Just like any standard Android menu. Here´s the file /res/menu/bottom_nav_menu.xml:

<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/page_home"
        android:icon="@drawable/baseline_home_black_24"
        android:title="@string/bottom_nav_home" />
    <item
        android:id="@+id/page_fav"
        android:icon="@drawable/baseline_favorite_black_24"
        android:title="@string/bottom_nav_fav" />
    <item
        android:id="@+id/page_search"
        android:icon="@drawable/baseline_search_black_24"
        android:title="@string/bottom_nav_search" />
    <item
        android:id="@+id/page_settings"
        android:icon="@drawable/baseline_app_settings_alt_black_24"
        android:title="@string/bottom_nav_settings" />
</menu>

The menu has four entries or items, each with a label and an icon from the official Material Design website.

There’s nothing special about the activity code. It inflates the layout and sets the Toolbar as the app action bar:

package com.danielme.android.bottomnavigation;

import android.os.Bundle;

import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;

public class MainActivity extends AppCompatActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    setupToolbar();
  }

  private void setupToolbar() {
    Toolbar toolbar = findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);
  }

}

The last element we need is the UI theme. From those provided by Material Components, I’ve chosen DayNight. It supports both light and dark themes at the same time. More specifically, I picked a variant that ignores the system action bar since we have a Toolbar and style it. As usual in my tutorials, I’ve set purple as the app’s primary color.

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
        <item name="colorPrimary">@color/primary</item>
        <item name="colorPrimaryDark">@color/primaryDark</item>
        <item name="toolbarStyle">@style/Widget.MaterialComponents.Toolbar.PrimarySurface</item>
    </style>

</resources>

That’s all! Run the project and check out the menu.

android bottomnavigation default

The items only show the icon; the label is reserved for the selected item. This happens when the menu has four or more items. With fewer items, icons and labels are always displayed:

You may change this behavior with the labelVisibilityMode property:

  • app:labelVisibilityMode=»unlabeled«. Never show the label.
    android bottomnavigation unlabeled
  • app:labelVisibilityMode=»labeled«. Always show the label.
    android bottomnavigation labeled
  • app:labelVisibilityMode=»selected«. Only display the label of the selected item. It’s the default behavior for menus with four or more items.

As for the colors, the navigation bar takes the one set in the variable ?attr/colorSurface. The selected item (icon and label) is highlighted with the primary color of the theme; the others, with ?attr/colorOnSurface.

The style is highly customizable. The official documentation details the properties you can use.

Implementing navigation with OnNavigationItemSelectedListener

We have a bottom menu—but it doesn’t do anything! Let’s bring the menu to life.

Screen with fragment

Let’s get each option to show a fragment in the center of the screen that, in turn, displays the icon corresponding to the chosen item.

A fragment is a reusable and combinable component that encapsulates a graphical interface and «lives» inside the activities. Like the latter, it’s a class that manages a layout according to a well-defined lifecycle.

The fragment layout:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
 
    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        tools:srcCompat="@drawable/baseline_app_settings_alt_black_48" />
 
</FrameLayout>

PageFragment takes the icon identifier and shows the layout:

package com.danielme.android.bottomnavigation.fragments;
 
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
 
import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
 
import com.danielme.android.bottomnavigation.R;
 
public class PageFragment extends Fragment {
 
  private static final String ARG_ICON = "ARG_ICON";
 
  public static PageFragment newInstance(@DrawableRes int iconId) {
    PageFragment frg = new PageFragment();
 
    Bundle args = new Bundle();
    args.putInt(ARG_ICON, iconId);
    frg.setArguments(args);
 
    return frg;
  }
 
  @Override
  public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable
          Bundle savedInstanceState) {
    View layout = inflater.inflate(R.layout.fragment_page, container, false);
    int icon = getArguments().getInt(ARG_ICON);
    layout.findViewById(R.id.imageView).setBackgroundResource(icon);
    return layout;
  } 

If you have never worked with fragments before, I’ll clarify some points.

Android can only instantiate a fragment via its no-args constructor, so that’s the only constructor a fragment should have. For this reason, PageFragment uses the static constructor technique. It offers a convenient way to create fragments that accept parameters without writing constructors. And while you overcome this limitation with FragmentFactory, using static constructors is simpler.

The technique proposes encapsulating the fragment creation logic in a static and public method, usually called newInstance. Its main purpose is to convert the input parameters of the fragment (the newInstance parameters) into arguments retrievable with getArguments. Thus, the onCreateView method can obtain with getArguments the parameters sent to the fragment—the identifier of the image.

How do you include the fragment on the screen? You need a container into which you embed the fragment with code. For example, a FrameLayout:

<com.google.android.material.appbar.MaterialToolbar
    android:id="@+id/toolbar"
   android:layout_width="match_parent"
   android:layout_height="?attr/actionBarSize"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />
 
<FrameLayout
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    app:layout_constraintBottom_toTopOf="@+id/bottom_navigation"
    app:layout_constraintTop_toBottomOf="@id/toolbar" />
 
 
<com.google.android.material.bottomnavigation.BottomNavigationView
    android:id="@+id/bottom_navigation"

The listener

Now it’s time to assemble all the pieces. To do this, capture and process the tapping of the menu items in a listener of type OnNavigationItemSelectedListener bound to the BottomNavigationView:

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  setupToolbar();
  setupBottomMenu();
}

private void setupBottomMenu() {
  bottomNavigationView = findViewById(R.id.bottom_navigation);
  bottomNavigationView.setOnItemSelectedListener(this::onItemSelectedListener);
  //muestra el fragment inicial cuando la aplicación se inicia
  bottomNavigationView.setSelectedItemId(R.id.page_home);
}

  private boolean onItemSelectedListener(MenuItem item) {
    switch (item.getItemId()) {
      case R.id.page_home -> {
        showPageFragment(R.drawable.baseline_home_black_48, R.string.bottom_nav_home);
        return true;
      }
      case R.id.page_fav -> {
        showPageFragment(R.drawable.baseline_favorite_black_48, R.string.bottom_nav_fav);
        return true;
      }
      case R.id.page_search -> {
        showPageFragment(R.drawable.baseline_search_black_48, R.string.bottom_nav_search);
        return true;
      }
      case R.id.page_settings -> {
        showPageFragment(R.drawable.baseline_app_settings_alt_black_48, R.string.bottom_nav_settings);
        return true;
      }
      default -> throw new IllegalArgumentException("item not implemented : " + item.getItemId());
    }
  }
 
  private void showPageFragment(@DrawableRes int iconId, @StringRes int title) {
    Fragment frg = PageFragment.newInstance(iconId);
    getSupportFragmentManager()
            .beginTransaction()
            .setCustomAnimations(R.anim.bottom_nav_enter, R.anim.bottom_nav_exit)
            .replace(R.id.container, frg)
            .commit();
  }

Line 11 adds the listener to the menu. Since OnNavigationItemSelectedListener has only one method, I implemented it with a lambda expression. It consists of the typical switch that decides what to do according to the clicked menu item. In our case, build and display a PageFragment with the proper icon.

The latter is the purpose of the showPageFragment method. Its code creates the fragment (line 43); sets up a simple animation (line 46) declared in the files located in /res/anim; and places the fragment in the FrameLayout (line 47). The newly constructed fragment replaces and destroys any other fragment already in the FrameLayout.

A small yet important detail (line 13): at startup, the activity should show the fragment corresponding to the menu item selected by default.

The result:


Managing screen orientation changes

Another detail. Be polite with the users: preserve the current menu selection when the screen changes its orientation. The way to achieve this is the usual approach in these cases. Override the onSaveInstanceState method of the activity to store in a Bundle object what you want. That object will be the argument that receives onCreate.

The following MainActivity code fragment stores in the Bundle the identifier of the selected menu item. Then, it retrieves it in the onCreate method:

@Override
protected void onSaveInstanceState(Bundle outState) {
  super.onSaveInstanceState(outState);
  outState.putInt(SELECTION, bottomNavigationView.getSelectedItemId());
}  

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    setupToolbar();
    setupBottomMenu(savedInstanceState);
}
 
private void setupBottomMenu(Bundle savedInstanceState) {
  bottomNavigationView = findViewById(R.id.bottom_navigation);
  bottomNavigationView.setOnNavigationItemSelectedListener(item -> {
   ...
    }
  });
   //muestra el fragment inicial cuando la aplicación se inicia
  if (savedInstanceState == null) {
    bottomNavigationView.setSelectedItemId(R.id.page_home);
  } else {
     //restaura el item seleccionado antes de la rotación
    bottomNavigationView.setSelectedItemId(savedInstanceState.getInt(SELECTION));
  }
}

Indicators with BadgeDrawable

The menu icons can include an indicator called badge overlaid in a corner. The badge goal is to draw the user’s attention. The most typical example is to warn that messages are pending to be read in the notifications section.

bottomnavigationview badge

Material Components provides a badge mechanism seamlessly integrated with BottomNavigationView. To use it, call the BottomNavigationView#getOrCreateBadge method to obtain a BadgeDrawable object attached to the menu item you want. The object represents the item’s badge.

BadgeDrawable favBadge = bottomNavigationView.getOrCreateBadge(R.id.page_fav)

Once you get the BadgeDrawable object, you control the visibility of the badge with the setVisible method. By default, the badge remains hidden. Also, if you wish, with setNumber you add a number to the badge. This way, for instance, you can inform the user how many notifications are pending to be read.

Here’s a demonstration. Notice that setNumber already displays the badge, so calling setVisible is redundant:

bottomNavigationView.getOrCreateBadge(R.id.page_fav).setNumber(1000);
bottomNavigationView.getOrCreateBadge(R.id.page_settings).setVisible(true);
android bottomnavigation badget

Some considerations about the badge:

  • Its color is the colorError of the theme.
  • You customize the maximum number of characters in the number with the setMaxCharacterCount method.
  • You change its position with the setBadgeGravity method. The following image depicts the four available options, all declared as constants in BadgeDrawable:
Android BadgeDrawable setBadgeGravity
badgeDrawable.setBadgeGravity(BadgeDrawable.BOTTOM_END);

Show \ hide the bottom bar on scroll

Material Design allows you to show/hide the bottom bar when the user scrolls the screen’s content. This behavior is provided by the integration of BottomNavigationView with CoordinatorLayout.

Creating a recycler view

Let’s see a real-world example. First, let’s extend the project with a new fragment that displays a list using a RecyclerView. Since this task takes some “plumbing work” and is not the subject of this post, I’ll be brief.

1- The RecyclerView dependency:

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.google.android.material:material:1.2.1'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    implementation 'androidx.recyclerview:recyclerview:1.1.0'
    coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1'
}

2- The layout of the fragment containing the RecyclerView:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
 
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
 
</FrameLayout>

3- The layout of the list row:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="?android:attr/selectableItemBackground"
    android:gravity="center_vertical"
    android:minHeight="@dimen/list_height">
 
    <TextView
        android:id="@+id/textView"
        style="@android:style/TextAppearance.Medium"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:paddingStart="@dimen/standard_margin"
        android:paddingEnd="@dimen/standard_margin"
        tools:text="item" />
</FrameLayout>

4- The adapter takes a list of strings that it associates with the ViewHolder corresponding to the previous layout:

package com.danielme.android.bottomnavigation.fragments;
 
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
 
import androidx.recyclerview.widget.RecyclerView;
 
import com.danielme.android.bottomnavigation.R;
 
import java.util.List;
 
class SimpleTextRecyclerViewAdapter extends RecyclerView.Adapter<TextViewHolder> {
 
  private final List<String> items;
  private final LayoutInflater inflater;
 
  SimpleTextRecyclerViewAdapter(Context context, List<String> items) {
    this.inflater = LayoutInflater.from(context);
    this.items = items;
  }
 
  @Override
  public TextViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View view = inflater.inflate(R.layout.row_recycler_view, parent, false);
    return new TextViewHolder(view);
  }
 
  @Override
  public int getItemCount() {
    return items.size();
  }
 
  @Override
  public void onBindViewHolder(TextViewHolder holder, int position) {
    holder.bindText(items.get(position));
  }
 
}
package com.danielme.android.bottomnavigation.fragments;
 
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
 
import androidx.recyclerview.widget.RecyclerView;
 
import com.danielme.android.bottomnavigation.R;
 
class TextViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
 
  private final TextView textView;
 
  TextViewHolder(View itemView) {
    super(itemView);
    textView = itemView.findViewById(R.id.textView);
    itemView.setOnClickListener(this);
  }
 
  void bindText(String text) {
    textView.setText(text);
  }
 
  @Override
  public void onClick(View view) {
    Toast.makeText(view.getContext(), textView.getText(), Toast.LENGTH_SHORT).show();
  }
 

5- This is the fragment. It creates the list and sets a separator for the list rows:

package com.danielme.android.bottomnavigation.fragments;
 
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
 
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
 
import com.danielme.android.bottomnavigation.R;
 
import java.util.ArrayList;
import java.util.List;
 
public class RecyclerViewFragment extends Fragment {
 
  @Override
  public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable
          Bundle savedInstanceState) {
    View layout = inflater.inflate(R.layout.fragment_recycler_view, container, false);
 
    RecyclerView recyclerView = layout.findViewById(R.id.recycler_view);
    recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
 
    List<String> items = new ArrayList<>();
    for (int i = 0; i < 25; i++) {
      items.add("item " + i);
    }
 
    recyclerView.setAdapter(new SimpleTextRecyclerViewAdapter(getContext(), items));
 
    DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(),
            DividerItemDecoration.VERTICAL);
    recyclerView.addItemDecoration(dividerItemDecoration);
 
    return layout;
  }
 
}

6- This new menu item displays the list:

<item
        android:id="@+id/page_list"
        android:icon="@drawable/baseline_list_black_24"
        android:title="@string/bottom_nav_list" />

7- Finally, the bar listener must process the click on the previous item to display RecyclerViewFragment:

private boolean onItemSelectedListener(MenuItem item) {
    switch (item.getItemId()) {
      case R.id.page_home -> {
        showPageFragment(R.drawable.baseline_home_black_48, R.string.bottom_nav_home);
        return true;
      }
      case R.id.page_list -> {
        showFragment(new RecyclerViewFragment(), R.string.bottom_nav_list);
        return true;
      }
       ...

The magic of CoordinatorLayout

Let’s move on to the relevant. For the app to automatically hide the bottom menu when the listing scrolls, cast the magic of CoordinatorLayout. Make it the main container of the activity layout. Then, place the BottomNavigationView as a direct child.

<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/coordinatort"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
 
        <com.google.android.material.appbar.MaterialToolbar
            android:id="@+id/toolbar"
            style="@style/AppTheme.ToolbarMain" />
 
        <FrameLayout
            android:id="@+id/container"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
 
    </LinearLayout>
 
    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottom_navigation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        app:labelVisibilityMode="labeled"
        app:layout_behavior="@string/hide_bottom_view_on_scroll_behavior"
        app:menu="@menu/bottom_nav_menu" />
 
</androidx.coordinatorlayout.widget.CoordinatorLayout>

Notice the highlighted line. layout_behavior establishes how BottomNavigationView should act on scrolling. The value hide_bottom_view_on_scroll_behavior instructs CoordinatorLayout to hide the menu bar when the screen scrolls down and show it when the screen scrolls up. Note that the fragment with the recycler view is the only scrollable GUI component.

We got it! And all it takes is a simple rearrangement of the layout.

Do you also want to hide the toolbar? Place it within an AppBarLayout. It’s another Android Material component that supports the motion synchronization that CoordinatorLayout provides.

<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/coordinatort"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
 
    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
 
        <com.google.android.material.appbar.MaterialToolbar
            android:id="@+id/toolbar"
            style="@style/AppTheme.ToolbarMain"
            app:layout_scrollFlags="scroll|enterAlways" />
 
    </com.google.android.material.appbar.AppBarLayout>
 
    <FrameLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />
 
    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottom_navigation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        app:labelVisibilityMode="labeled"
        app:layout_behavior="@string/hide_bottom_view_on_scroll_behavior"
        app:menu="@menu/bottom_nav_menu" />
 
</androidx.coordinatorlayout.widget.CoordinatorLayout>

This video clip shows the simultaneous movements of Toolbar and BottomNavigationView.

The AndroidX Navigation framework (I’ll call it Navigation from now on) gives you a sophisticated and elegant way to configure the navigation throughout the application. Its purpose is to simplify and standardize the management of the navigation.

How? You define this navigation in an XML file editable with a visual tool. Then, with an API you integrate it with the GUI components involved.

Exploring the features of Navigation is beyond the scope of this humble tutorial. I’ll focus on its integration with our bottom menu.

Start by adding two dependencies:

  • Java:
implementation 'androidx.navigation:navigation-fragment:2.5.3'
implementation 'androidx.navigation:navigation-ui:2.5.3'
  • Kotlin:
implementation "androidx.navigation:navigation-fragment-ktx:2.5.3"
implementation "androidx.navigation:navigation-ui-ktx:2.5.3"

In the /res/navigation/nav.xml file you declare the navigation graph with all the screens. In our case, the five fragments accessible from the menu:

<?xml version="1.0" encoding="utf-8"?>
<navigation 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:id="@+id/nav"
    app:startDestination="@id/page_home">

    <fragment
        android:id="@+id/page_home"
        android:name="com.danielme.android.bottomnavigation.fragments.PageFragment"
        tools:layout="@layout/fragment_page">
        <argument
            android:name="ARG_ICON"
            android:defaultValue="@drawable/baseline_home_black_48"
            app:argType="reference" />
    </fragment>

    <fragment
        android:id="@+id/page_list"
        android:name="com.danielme.android.bottomnavigation.fragments.RecyclerViewFragment"
        tools:layout="@layout/fragment_recycler_view" />

    <fragment
        android:id="@+id/page_fav"
        android:name="com.danielme.android.bottomnavigation.fragments.PageFragment"
        tools:layout="@layout/fragment_page">
        <argument
            android:name="ARG_ICON"
            android:defaultValue="@drawable/baseline_favorite_black_48"
            app:argType="reference" />
    </fragment>

    <fragment
        android:id="@+id/page_search"
        android:name="com.danielme.android.bottomnavigation.fragments.PageFragment"
        tools:layout="@layout/fragment_page">
        <argument
            android:name="ARG_ICON"
            android:defaultValue="@drawable/baseline_search_black_48"
            app:argType="reference" />
    </fragment>

    <fragment
        android:id="@+id/page_settings"
        android:name="com.danielme.android.bottomnavigation.fragments.PageFragment"
        tools:layout="@layout/fragment_page">
        <argument
            android:name="ARG_ICON"
            android:defaultValue="@drawable/baseline_app_settings_alt_black_48"
            app:argType="reference" />
    </fragment>

</navigation>

Each screen \ fragment declared in the file is a destination of the navigation and uses these parameters:

  • android:id. The identifier of the navigation destination.
  • android:name. The full name of the class that manages the destination.
  • android:label. The name of the destination. The action bar will display it.
  • tools:layout. The layout of the fragment, informative data for Android Studio.

Two aspects require special mention:

  • In startDestination you set the initial navigation screen. In the example, it’s the one that will be displayed every time you start the activity.
  • PageFragment expects an argument with the identifier of the image to display in its ImageView. You supply that argument with the argument tag.

Each navigation destination’s identifier must match the menu item’s identifier that shows that destination.

In the activity layout, you need the NavHostFragment component for Navigation to show the fragments with the navigation destinations:

    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <com.google.android.material.appbar.MaterialToolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            app:layout_scrollFlags="scroll|enterAlways" />

    </com.google.android.material.appbar.AppBarLayout>

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/nav"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottom_navigation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        app:labelVisibilityMode="labeled"
        app:layout_behavior="@string/hide_bottom_view_on_scroll_behavior"
        app:menu="@menu/bottom_nav_menu" />

You declare NavHostFragment in a FragmentContainerView. In the latter, you must activate defaultNavHost. This setting ensures that the NavHostFragment handles the Back button. Also, remember to specify the navigation file with the navGraph property.

One last thing to do. Link the navigation and the menu with the NavigationUI#setupWithNavController method:

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    setupToolbar();
    //setupBottomMenu(savedInstanceState);
    setupNavigation();
  }

  private void setupNavigation() {
    BottomNavigationView bottomNavigationView = findViewById(R.id.bottom_navigation);
    NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
    NavigationUI.setupWithNavController(bottomNavigationView,
            navHostFragment.getNavController());
  }

I commented out the setupBottomMenu method as unnecessary. Navigation framework now takes care of navigation and preserving the menu state when rotating the device.

After the previous changes, the behavior of the sample application doesn’t change—but now you centralize and configure the navigation in the nav.xml file. Plus, you skip all the code with the functions Navigation takes care of. In conclusion, you made a good deal.

Note. The integration with Navigation is commented on in the code hosted on GitHub. The navigation implemented with the listener is enabled.

Action bar integration

One feature I’ve left out because not all projects require it is the display in the action bar of the screen title. It’s as easy as calling the activity setTitle method when you replace the fragment:

private void showPageFragment(@DrawableRes int iconId, @StringRes int title) {
    Fragment frg = PageFragment.newInstance(iconId);
    getSupportFragmentManager()
            .beginTransaction()
            .setCustomAnimations(R.anim.bottom_nav_enter, R.anim.bottom_nav_exit)
            .replace(R.id.container, frg)
            .commit();
    setTitle(title);
  }

If you use Navigation framework, ask it to manage the Toolbar:

AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(R.id.page_home,
            R.id.page_fav, R.id.page_list, 
            R.id.page_settings, R.id.page_search)
            .build();
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);

You should build the AppBarConfiguration object by providing it with the top-level navigation destinations. Otherwise the action bar displays the Back button, as shown in the picture below.

That was it. I hope I’ve been helpful. Again, I leave you the video with the result of the demo project.

Source code

The sample project is available on GitHub. For more information, consult this tutorial:

How to import repositories from GitHub with Git, Eclipse and Android Studio\IntelliJ.

Deja un comentario

Este sitio utiliza Akismet para reducir el spam. Conoce cómo se procesan los datos de tus comentarios.