
La especificación de Material Design define un tipo de menú de navegación que en los últimos tiempos está adquiriendo gran popularidad. Lo verás en aplicaciones tan exitosas como Spotify o Instagram, además de en las propias de Google (YouTube, Google Play…). Se trata de una barra con iconos —pueden incluir un texto— situada en la parte inferior de la pantalla que muestra entre tres y cinco elementos de navegación. A veces, también incluye acciones.

Estamos ante un componente gráfico tan básico que todo programador Android debe conocer. En este tutorial aprenderás a configurar una barra de navegación inferior con las características más demandadas. Usaremos la implementación oficial de Google, llamada BottomNavigationView, incluida en Material Components For Android.
Índice
- Proyecto de ejemplo con BottomNavigationView
- Implementando la navegación con un listener
- Badges (insignias)
- Ocultar\mostrar el menú al hacer scroll
- Integración con el framework Navigation de AndroidX
- Integración con Toolbar
- Código de ejemplo
Nota. Si tienes más de cinco elementos de navegación, un menú lateral resulta más apropiado 😉
Proyecto de ejemplo con BottomNavigationView
La versión actual del proyecto de ejemplo utiliza lo último disponible en febrero de 2023:
- Android API 33
- Gradle 7.5 (requiere Java 11)
- Android Studio Electric Eel
- Material Components 1.8
El lenguaje es Java, pero veremos poco código y muy simple, así que no tendrás problemas en adaptarlo a Kotlin si prefieres este lenguaje.
Este es el build.properties:
apply plugin: 'com.android.application'
android {
compileSdkVersion 33
defaultConfig {
applicationId "com.danielme.android.bottomnavigation"
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_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.google.android.material:material:1.8.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation "androidx.recyclerview:recyclerview:1.2.1"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.0'
}
El proyecto, sencillo pero realista, tiene una única Activity o actividad. Vamos primero con su layout. Consta de una Toolbar arriba y un BottomNavigationView abajo, ambos posicionados dentro de un 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>
La línea 23 establece el menú de navegación. ¿Cómo lo creamos? Pues como cualquier menú estándar de Android. Aquí lo tienes (fichero /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>
Tenemos cuatro entradas o items de menú, cada una con su título e icono. Los iconos los he descargado de la página oficial de Material Design.
La actividad no tiene ninguna complicación. Se limita a «inflar» el layout y establecer la Toolbar como ActionBar:
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);
}
}
El último elemento que nos falta es el tema gráfico de la aplicación. De los disponibles en Material Components, elegiré DayNight, el que brinda soporte automático para tema claro y oscuro. En concreto, la variación que no contempla a la ActionBar del sistema porque hemos definido nuestra propia Toolbar a la que daremos un estilo (Widget.MaterialComponents.Toolbar.PrimarySurface). Como es habitual en mis tutoriales, me limitaré a declarar un color púrpura como el principal de la aplicación:
<?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>
¡Listo! Si ejecutas el proyecto verás el menú. 😍

Como puedes ver, las entradas del menú solo muestran el icono. El título queda reservado para el elemento seleccionado. Este comportamiento se cambia con la propiedad labelVisibilityMode:
- app:labelVisibilityMode=»unlabeled«. Nunca se mostrará el texto del icono.
- app:labelVisibilityMode=»labeled«. El texto aparece siempre.
En lo que respecta a los colores, la barra tiene el definido por la variable ?attr/colorSurface. La entrada del menú seleccionada (icono y texto) se resalta con el color principal del tema, mientras que las demás se colorean con ?attr/colorOnSurface. Los estilos son muy configurables; las propiedades personalizables se describen con detalle en la documentación oficial.
Implementando la navegación con un listener
Démosle vidillla al menú. Queremos que cada entrada muestre un Fragment o fragmento en el centro de la pantalla. Así, tendremos un auténtico menú de navegación.
Siempre crearemos un mismo tipo de fragmento que mostrará en su centro el icono de la entrada del menú seleccionada. Este es su 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>
El código del fragmento obtiene el identificador del icono (un argument) y muestra el 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);
layout.findViewById(R.id.imageView).setBackgroundResource(getArguments().getInt(ARG_ICON));
return layout;
}
La creación de PageFragment la haremos con el típico «constructor estático» porque un fragmento debe crearse siempre mediante su constructor vacío (a menos que trabajemos con FragmentFactory), y queremos una forma obvia de crearlo a partir del identificador del icono. Toda la lógica queda encapsulada en newInstance.
Añadimos un contenedor a la actividad en el que insertar PageFragment:
<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"
Ahora viene lo interesante. Recibimos la pulsación o toque en los elementos del menú en un listener de tipo OnNavigationItemSelectedListener si lo vinculamos al componente 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.setOnNavigationItemSelectedListener(item -> {
switch (item.getItemId()) {
case R.id.page_home:
showFragment(PageFragment.newInstance(R.drawable.baseline_home_black_48));
break;
case R.id.page_fav:
showFragment(PageFragment.newInstance(R.drawable.baseline_favorite_black_48));
break;
case R.id.page_search:
showFragment(PageFragment.newInstance(R.drawable.baseline_search_black_48));
break;
case R.id.page_settings:
showFragment(PageFragment.newInstance(R.drawable.baseline_app_settings_alt_black_48));
break;
default:
throw new IllegalArgumentException("item not implemented : " + item.getItemId());
}
return true;
});
//setear aquí para que el listener muestre el fragment inicial al cargarse la pantalla
bottomNavigationView.setSelectedItemId(R.id.page_home);
}
private void showFragment(Fragment frg) {
getSupportFragmentManager()
.beginTransaction()
.setCustomAnimations(R.anim.bottom_nav_enter, R.anim.bottom_nav_exit)
.replace(R.id.container, frg)
.commit();
}
Tenemos el clásico switch que decide qué hacer según el elemento del menú que fue pulsado. En nuestro caso, crear y mostrar en pantalla un PageFragment con el icono adecuado aplicando una sencilla animación (línea 37) decalrada en los ficheros de /res/anim. Eso sí, hay un pequeño detalle (línea 31) del que no podemos olvidarnos: al cargarse la actividad debe mostrarse el fragmento correspondiente al elemento del menú seleccionado por omisión.
Otro aspecto a considerar es que cuando rotamos la pantalla deberíamos conservar la selección actual y evitar que se muestre el fragmento inicial. La técnica para conseguirlo es la habitual en estos casos: sobrescribir el método onSaveInstanceState de la actividad para guardar en un objeto Bundle lo que queramos. Ese objeto será, ni más ni menos, que el parámetro de entrada de onCreate.
En esta implementación, guardo en el Bundle el identificador del item del menú seleccionado y luego lo recupero, si existe, en onCreate:
@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 -> {
...
}
});
//setear aquí para que el listener muestre el fragment inicial al cargarse la pantalla
if (savedInstanceState == null) {
bottomNavigationView.setSelectedItemId(R.id.page_home);
} else {
bottomNavigationView.setSelectedItemId(savedInstanceState.getInt(SELECTION));
}
}
Badges (insignias)
Las entradas de menú pueden mostrar un indicador, denominado badge o insignia, para llamar la atención del usuario. El caso de uso más frecuente es informar que en la sección de notificaciones existen mensajes pendientes de leer.

Material Components provee un mecanismo de insignias integrado a la perfección con BottomNavigationView. Llamando a BottomNavigationView#getOrCreateBadge obtenemos un objeto BadgeDrawable asociado al item del menú que queramos. Con ese objeto mostraremos y ocultaremos a conveniencia el indicador del badge (método setVisible) para el item. También es posible lograr que el badge muestre un número (método setNumber) que indique, por ejemplo, las notificaciones pendientes de atender.
Aquí tienes una demostración:
bottomNavigationView.getOrCreateBadge(R.id.page_fav).setNumber(1000);
bottomNavigationView.getOrCreateBadge(R.id.page_settings).setVisible(true);

Algunas consideraciones acerca del indicador:
- Su color es el colorError del tema
- El número máximo de caracteres se personaliza con el método setMaxCharacterCount
- Puedes cambiar su posición con el método setBadgeGravity. En la siguiente captura tienes las cuatro opciones, definidas como constantes en BadgeDrawable:

badgeDrawable.setBadgeGravity(BadgeDrawable.BOTTOM_END);
Ocultar\mostrar el menú al hacer scroll
Aunque no lo he visto en muchas aplicaciones, Material Design contempla la posibilidad de ocultar\mostrar automáticamente el menú inferior al realizarse scroll o desplazar ciertos componentes de la pantalla como un RecyclerView o ScrollView.
Este comportamiento lo proporciona la integración de BottomNavigationView con CoordinatorLayout. Se configura de modo similar a otros componentes gráficos como los botones FAB o las estructuras basadas en AppbarLayout (toolbar con pestañas, imágenes de fondo, etc) que también cuentan con integración directa con CoordinatorLayout para reaccionar ante el desplazamiento de otros elementos de la interfaz gráfica.
En esta sección veremos cómo aplicar ese comportamiento a un ejemplo realista. Por ello, ampliemos primero el proyecto con un nuevo fragmento que visualice un listado con un RecyclerView. Puesto que tengo publicado un par de tutoriales sobre RecyclerView y no es algo relevante para el presente tutorial, me limito a mostrar el código:
1- Añadimos la dependencia de RecyclerView:
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- Creamos el layout del fragmento que contendrá el 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- Cada fila del listado consiste en un TextView:
<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- En el Adapter recibimos una lista de cadenas que asociamos al ViewHolder correspondiente al layout anterior:
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- Esta es la implementación del fragmento. Crea la lista para el adapter y configura un separador:
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- Para mostrar RecyclerViewFragment agregamos una entrada al menú:
<item
android:id="@+id/page_list"
android:icon="@drawable/baseline_list_black_24"
android:title="@string/bottom_nav_list" />
7- Finalizamos el desarrollo lanzando el RecyclerViewFragment dentro del listener del NavigationBottomView:
bottomNavigationView.setOnNavigationItemSelectedListener(item -> {
switch (item.getItemId()) {
case R.id.page_home:
showFragment(PageFragment.newInstance(R.drawable.baseline_home_black_48));
break;
case R.id.page_list:
showFragment(new RecyclerViewFragment());
break;
...
Vamos con lo importante. Para que el menú se oculte de manera automática al deslizarse el listado, recurrimos, como ya dije, a la magia de CoordinatorLayout. Tenemos que modificar el layout de la actividad de tal modo que BottomNavigationView sea hijo directo del mismo:

<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>
Fíjate en la línea resaltada: informa a BottomNavigationView cómo debe actuar ante el scroll.
Hemos logrado nuestro objetivo con un sencillo cambio en el layout. Una simplicidad digna de aplauso 👏
¿Quieres que también se oculte la Toolbar? Ponla dentro de un AppBarLayout, otro de los elementos compatibles con CoordinatorLayout:

<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>
El siguiente video enseña el recogimiento simultáneo de Toolbar y BottomNavigationView:
Integración con el framework Navigation de AndroidX
Existe una manera más sofisticada y elegante de configurar la navegación desde el menú inferior —y, en general, en toda la aplicación—: usando el framework Navigation de AndroidX. Simplifica y centraliza la gestión de la navegación a través de todas las pantallas de una aplicación. La navegación se define en un fichero XML editable con una herramienta visual, y con una API integraremos esa navegación con los componentes gráficos susceptibles de estar implicados.
Explorar las posibilidades de Navigation queda fuera del alcance de este modesto tutorial. Me centraré en su integración con nuestro menú.
Lo primero es añadir dos dependencias:
implementation 'androidx.navigation:navigation-fragment:2.5.3'
implementation 'androidx.navigation:navigation-ui:2.5.3'
En el fichero /res/navigation/nav.xml se declara el grafo de navegación con todas las pantallas (los cinco fragments accesibles desde el menú):
<?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>
Hay dos aspectos que requieren una mención especial:
- En startDestination se establece la pantalla inicial de navegación. En nuestro ejemplo, será la que se muestre cada vez que arranque la actividad.
- PageFragment espera recibir un argumento con el identificador de la imagen que debe mostrar en su ImageView. Establecemos ese argumento para cada caso con un bloque <argument>.
El identificador que ves en cada destino de navegación no es casual: debe coincidir con el identificador del item del menú que muestra ese destino.

En el layout de la actividad se precisa el componente NavHostFragment para que Navigation ponga en él los fragments:
<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"/>
El fragmento se declara en un FragmentContainerView indicando el nombre completo de la clase en la propiedad. También hay que activar defaultNavHost para que este fragmento gestione la pulsación del botón Atrás o back del sistema e indicar el fichero que define la navegación.
¡Ya casi estamos! Nos queda vincular en código nuestro sistema de navegación con el menú. Debemos invocar al método NavigationUI#setupWithNavController:
@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());
}
He comentado el método setupBottomMenu porque ya no se requieren sus operaciones. Ahora, la navegación y la preservación del estado del menú al rotar el dispositivo son asuntos de Navigation.
No pongo un vídeo del resultado dado que el funcionamiento de la aplicación no ha cambiado.
Nota. La integración con Navigation está comentada en el proyecto alojado en GitHub; he dejado activa la navegación implementada con el listener.
Integración con Toolbar
Una funcionalidad que he obviado porque no todos los proyectos la requieren es la visualización en la Toolbar del título de la pantalla seleccionada con el menú. Empleando el listener es tan fácil como llamar al método setTitle de la actividad:
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;
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) {
showFragment(PageFragment.newInstance(iconId), title);
}
private void showFragment(Fragment frg, @StringRes int title) {
getSupportFragmentManager()
.beginTransaction()
.setCustomAnimations(R.anim.bottom_nav_enter, R.anim.bottom_nav_exit)
.replace(R.id.container, frg)
.commit();
setTitle(title);
}
Con Navigation, y al igual que ya hicimos con el menú, vincularemos Navigation con la Toolbar para que tome el control del título:
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);
Si no construimos el objeto AppBarConfiguration con la lista de elementos de navegación de primer nivel, la Toolbar mostrará erróneamente el icono Atrás.

Ahora sí, este es el resultado definitivo del proyecto de ejemplo. Espero que te sea de ayuda en tus exitosas aventuras con Android. 💪
Código de ejemplo
El proyecto de ejemplo está publicado en GitHub. Para más información sobre cómo utilizar GitHub, consultar este artículo.
Tutoriales recomendados
Menu lateral Navigation drawer. Listeners. Navigation UI.
Toolbar, pestañas y ViewPager2 con AndroidX y Material Components