
Navigation Drawer es uno de los componentes visuales definidos por Material Design más importantes. Tal vez su nombre no te diga nada, pero lo conoces de sobra: el popular menú lateral deslizable desde la izquierda (la derecha en lenguajes RTL). Es el principal elemento de navegación de las aplicaciones móviles, si bien con los años ha ido perdiendo terreno en favor del menú inferior Bottom Navigation. Este último se recomienda cuando tenemos como máximo cinco elementos de navegación.
El menú Drawer suele encontrarse en la pantalla principal de la aplicación y contar con un botón «hamburguesa» (tres líneas horizontales) para desplegarlo. También es común que se permita su apertura deslizando el contenido de la pantalla desde su borde izquierdo.
La siguiente captura muestra el menú principal de una de mis apps

Más pronto que tarde te verás en la necesidad de incluir este tipo de menú en tus apps. Espero ayudarte con el presente tutorial en el que mostraré cómo usar la implementación que Google ofrece con las librerías AndroidX y Material Components. Veremos las funcionalidades más habituales, así como la integración con Navigation UI.
Índice
- Proyecto con Navigation Drawer
- Elementos del menú
- Cabecera o header del menú
- Implementando las acciones del menú con un listener
- Integración con Navigation component
- Personalización de estilos
- Resultado final
- Código de ejemplo
Proyecto con Navigation Drawer
Empecemos creando un proyecto de ejemplo. Empleará las últimas versiones disponibles en enero de 2023:
- Android API 33
- Gradle 7.5 (requiere Java 11)
- Android Studio Electric Eel
- Material Components 1.8
El lenguaje de programación es Java. No obstante, el código será tan simple que no tendrás problemas en adaptarlo a Kotlin si prefieres este lenguaje.
Advertencia. Quizás encuentres tutoriales y libros en los que se emplean las librerías Support Libraries para construir el menú. Es material muy desactualizado, pues estas librerías fueron abandonadas a finales de 2018 en favor de AndroidX y la iniciativa Android Jetpack.
Dependencias
En el build.properties del módulo app añadimos las dependencias de Material Components y ConstraintLayout:
apply plugin: 'com.android.application'
android {
compileSdkVersion 33
defaultConfig {
applicationId "com.danielme.android.navigationdrawer"
minSdkVersion 26
targetSdkVersion 33
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
namespace 'com.danielme.android.navigationdrawer'
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.google.android.material:material:1.8.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
}
DrawerLayout y NavigationView
Tendremos una única activity o actividad llamada HomeActivity. Usaremos DrawerLayout como el layout principal. Es un contenedor (ViewGroup) proporcionado por AndroidX para albergar los elementos relacionados con el menú lateral. Dentro de él colocaremos dos componentes de Material Components:
- La barra superior de la pantalla, la ActionBar de Android implementada por el componente MaterialToolbar. Está contenida en un LinearLayout en el que más adelante incrustaremos un Fragment (fragmento).
- El componente NavigationView. Es el menú lateral en sí.
Este es el fichero /res/layout/activity_home.xml con la primera versión del layout (lo retocaremos a lo largo del tutorial):
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:openDrawer="start">
<LinearLayout
android:id="@+id/home_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" />
</LinearLayout>
<com.google.android.material.navigation.NavigationView
android:id="@+id/navigation_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true" />
</androidx.drawerlayout.widget.DrawerLayout>
Configuración en la Activity
En la actividad, además de la Toolbar, configuramos el DrawerLayout con la clase ActionBarDrawerToggle. Su nombre da una pista de su cometido: integrar el menú del Drawer con la ActionBar. La integración consiste en mostrar el icono hamburguer en la ActionBar de tal modo que su pulsación deslice el menú para hacerlo visible. Si no configuramos este comportamiento, el menú solo se abrirá deslizando el lado izquierdo de la pantalla hacia la derecha.
El código a escribir resulta mecánico, una especie de plantilla reutilizable entre aplicaciones:
public class HomeActivity extends AppCompatActivity {
private DrawerLayout drawerLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(findViewById(R.id.toolbar));
drawerLayout = findViewById(R.id.drawer_layout);
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
this, drawerLayout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawerLayout.addDrawerListener(toggle);
toggle.syncState();
}

Por lo común, otro comportamiento deseable es el cierre del menú con la pulsación del botón Atrás o back de Android.
@Override
public void onBackPressed() {
if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
drawerLayout.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
}
}
Capturamos el evento sobrescribiendo el método onBackPressed. Su contenido es sencillo una vez conozcas los métodos de DrawerLayout que necesitamos para saber si el menú está abierto (isDrawerOpen) y, en ese caso, cerrarlo (closeDrawer). Cuando esté cerrado, llamaremos a super.onBackPressed() para que la acción Atrás reciba el comportamiento predeterminado —navegar un nivel hacia atrás en el historial de pantallas—. Dado que estamos en la pantalla inicial de la aplicación, la pulsación de Atrás la cerrará.
Tema visual
Ocupémonos de los estilos. En el fichero /res/values/themes.xml declaramos como tema global de la aplicación uno de tipo DayNight de Material Components. Son los que tienen soporte para tema claro y oscuro a la vez. En concreto, he escogido el que no se aplica a la ActionBar del sistema, ya que estamos definiendo nuestra propia Toolbar y le daremos un estilo personalizado. Toda esta configuración se describe en el tutorial correspondiente. Naturalmente, eres libre de elegir el tema que más te convenga.
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<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>
<item name="drawerArrowStyle">@style/AppTheme.DrawerArrowStyle</item>
</style>
<style name="AppTheme.DrawerArrowStyle" parent="Widget.AppCompat.DrawerArrowToggle">
<item name="color">@android:color/white</item>
</style>
</resources>
Atención a la propiedad drawerArrowStyle: define el estilo para el icono hamburguesa. Quiero que sea blanco.
Con los estilos anteriores, el menú da la sensación de ser tapado por la status bar de Android.

El diseño habitual muestra el menú debajo de la status bar con un efecto translúcido. Conseguirlo es trivial:
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="colorPrimary">@color/primary</item>
<item name="colorPrimaryDark">@color/primaryDark</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="toolbarStyle">@style/Widget.MaterialComponents.Toolbar.PrimarySurface</item>
<item name="drawerArrowStyle">@style/AppTheme.DrawerArrowStyle</item>
</style>

Ya tenemos el menú. Literalmente, un lienzo en blanco en el que añadiremos los distintos elementos de navegación.
Elementos del menú
Demos contenido al menú. Se define como cualquier otro: en un XML, ubicado en la carpeta /res/menu, con elementos de tipo item que se corresponden con cada entrada del menú.
Este es el menú del proyecto de ejemplo (fichero activity_home_navigation_drawer.xml). Define cinco elementos con un texto y un icono:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:showIn="navigation_view">
<group android:checkableBehavior="single">
<item
android:id="@+id/nav_camera"
android:icon="@drawable/ic_menu_camera"
android:title="@string/menu_camera" />
<item
android:id="@+id/nav_gallery"
android:icon="@drawable/ic_menu_gallery"
android:title="@string/menu_gallery" />
<item
android:id="@+id/nav_tools"
android:icon="@drawable/ic_menu_manage"
android:title="@string/menu_tools" />
<item
android:id="@+id/nav_share"
android:icon="@drawable/ic_menu_share"
android:title="@string/menu_share" />
<item
android:id="@+id/nav_send"
android:icon="@drawable/ic_menu_send"
android:title="@string/menu_send" />
</group>
</menu>
Usa nombres concisos y descriptivos. Si un nombre es demasiado largo, se truncará. Y aunque no es obligatorio, los iconos me parecen imprescindibles para facilitar el uso del menú.
Vinculamos menú y Navigation Drawer así:
<com.google.android.material.navigation.NavigationView
android:id="@+id/navigation_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:menu="@menu/activity_home_navigation_drawer" />
¡Tachán! Aquí tenemos los items:

Los items pueden organizarse en secciones, con o sin título, divididas por un pequeño separador. La idea es agrupar los elementos del menú relacionados para que el usuario encuentre rápidamente la entrada que necesita, algo recomendable cuando el menú es extenso.
Cada sección es un bloque <group> con los items que abarca. Si la sección precisa de un título, ponemos su group dentro de un bloque <menu> que, a su vez, irá dentro de un elemento <item> en el que se define dicho título. Para que lo veas claro, organizaré el menú de ejemplo en tres secciones, una de ellas con un título:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:showIn="navigation_view">
<group android:checkableBehavior="single">
<item
android:id="@+id/nav_camera"
android:icon="@drawable/ic_menu_camera"
android:title="@string/menu_camera" />
<item
android:id="@+id/nav_gallery"
android:icon="@drawable/ic_menu_gallery"
android:title="@string/menu_gallery" />
</group>
<group
android:id="@+id/group1"
android:checkableBehavior="single">
<item
android:id="@+id/nav_tools"
android:icon="@drawable/ic_menu_manage"
android:title="@string/menu_tools" />
</group>
<item android:title="@string/menu_group">
<menu>
<group android:checkableBehavior="single">
<item
android:id="@+id/nav_share"
android:icon="@drawable/ic_menu_share"
android:title="@string/menu_share" />
<item
android:id="@+id/nav_send"
android:icon="@drawable/ic_menu_send"
android:title="@string/menu_send" />
</group>
</menu>
</item>
</menu>
Esta captura muestra la estructura anterior en el editor visual de Android Studio:

El resultado final:

Cabecera o header del menú
El menú va tomando forma. Sigamos avanzando. 💪
En general, los menús laterales disponen de una cabecera o header que muestra información sobre la aplicación y el usuario, si lo hubiera. Este header es un layout cualquiera, así que contendrá lo que queramos. El componente NavigationView lo insertará en la parte superior del menú.
A modo de ejemplo, usemos el siguiente header (fichero /res/layout/nav_header.xml). Es un ConstraintLayout que apila un TextView con el icono y nombre de la app, y otro TextView con un texto de copyright:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:background="@android:color/holo_blue_dark">
<TextView
android:id="@+id/header_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:layout_marginTop="@dimen/activity_horizontal_margin"
android:drawablePadding="12dp"
android:gravity="center_vertical"
android:text="@string/app_name"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Medium.Inverse"
app:drawableStartCompat="@mipmap/ic_launcher"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
android:text="@string/copyright"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Small.Inverse"
app:layout_constraintEnd_toEndOf="@+id/header_title"
app:layout_constraintStart_toStartOf="@+id/header_title"
app:layout_constraintTop_toBottomOf="@+id/header_title" />
</androidx.constraintlayout.widget.ConstraintLayout>
Declaramos el header en la propiedad headerLayout de NavigationMenu:
<com.google.android.material.navigation.NavigationView
android:id="@+id/navigation_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="@layout/nav_header"
app:menu="@menu/activity_home_navigation_drawer" />
Así queda:

Hay un detalle que puede pasar desapercibido: en ConstraintLayout activé fitsSystemWindows para que el layout reciba un relleno o padding que lo separe de la status bar. Si no lo hubiera hecho, ocurrirá el siguiente efecto indeseable, peor cuanto mayor sea la status bar:

Si fuera necesario acceder en el código al header, pediremos el componente con el método NavigationView#getHeaderView. En consecuencia, el header puede ser interactivo y su contenido dinámico.
Por ejemplo, mostremos un Toast cuando el usuario pulse el TextView con el título:
View header = navigationView.getHeaderView(0);
header.findViewById(R.id.header_title).setOnClickListener(view -> Toast.makeText(
HomeActivity.this,
getString(R.string.title_click),
Toast.LENGTH_SHORT).show());
Implementando las acciones del menú con un listener
Nuestro menú es inútil: si pulsamos una de sus entradas, ¡no ocurre nada! Pongamos remedio ipso facto.
La captura y tratamiento de los elementos pulsados se realiza en un oyente o listener implementado con la interfaz NavigationView.OnNavigationItemSelectedListener. Su único método tiene como entrada el elemento que fue pulsado:
public interface OnNavigationItemSelectedListener {
public boolean onNavigationItemSelected(@NonNull MenuItem item);
}
Démosle vida al menú haciendo algo útil cuando se toquen sus entradas. Mostraremos un fragmento (HomeContentFragment), con el nombre del item pulsado, añadiéndolo al LinearLayout que contiene la Toolbar. Con otras palabras, nos quedamos en HomeActivity y cambiamos el contenido de la pantalla, así como el título de la Toolbar.
Este es el diseño gráfico del fragmento:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/darker_gray"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/text"
style="@android:style/TextAppearance.Large"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold" />
</LinearLayout>
Y este su código:
package com.danielme.android.navigationdrawer;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.fragment.app.Fragment;
public class HomeContentFragment extends Fragment {
private static final String TEXT_ID = "text_id";
public static HomeContentFragment newInstance(@StringRes int textId) {
HomeContentFragment frag = new HomeContentFragment();
Bundle args = new Bundle();
args.putInt(TEXT_ID, textId);
frag.setArguments(args);
return frag;
}
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable
Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.home_fragment, container, false);
if (getArguments() != null) {
String text = getString(getArguments().getInt(TEXT_ID));
((TextView) layout.findViewById(R.id.text)).setText(text);
} else {
throw new IllegalArgumentException("Argument " + TEXT_ID + " is mandatory");
}
return layout;
}
}
HomeContentFragment recupera el texto a mostrar desde los argumentos y lo establece en el layout. Seguí la técnica del constructor estático para encapsular la creación del fragmento y el paso del argumento (los fragmentos deben instanciarse mediante su constructor vacío).
Vayamos a lo más importante: la implementación de OnNavigationItemSelectedListener. Por comodidad, la he realizado en HomeActivity:
public class HomeActivity extends AppCompatActivity
implements NavigationView.OnNavigationItemSelectedListener {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
int titleId = getTitle(menuItem);
showFragment(titleId);
drawerLayout.closeDrawer(GravityCompat.START);
return true;
}
private int getTitle(@NonNull MenuItem menuItem) {
switch (menuItem.getItemId()) {
case R.id.nav_camera:
return R.string.menu_camera;
case R.id.nav_gallery:
return R.string.menu_gallery;
case R.id.nav_tools:
return R.string.menu_tools;
case R.id.nav_share:
return R.string.menu_share;
case R.id.nav_send:
return R.string.menu_send;
default:
throw new IllegalArgumentException("menu option not implemented!!");
}
private void showFragment(@StringRes int titleId) {
Fragment fragment = HomeContentFragment.newInstance(titleId);
getSupportFragmentManager()
.beginTransaction()
.setCustomAnimations(R.anim.nav_enter, R.anim.nav_exit)
.replace(R.id.home_content, fragment)
.commit();
setTitle(getString(titleId));
}
}
Aunque aparece bastante código, no tiene nada de especial. En esencia, se trata del típico switch que evalúa las opciones de un menú. En nuestro caso, selecciona el texto que mostrará HomeContentFragment en su centro. Lo usaremos en el método showFragment para crear el fragmento, el cual se incrusta en home_content, y mostrarlo como el título de la Toolbar.
Es imprescindible asociar el listener al NavigationView en el onCreate:
NavigationView navigationView = findViewById(R.id.navigation_view);
navigationView.setNavigationItemSelectedListener(this);
Tampoco podemos pasar por alto la selección de la pantalla inicial. Al arrancarse, HomeActivity debe mostrar el primer elemento del menú, y que en nuestro ejemplo es el fragmento para la opción «Cámara». Simulemos la pulsación de dicho elemento con el siguiente código sito en el método onCreate:
MenuItem menuItem = navigationView.getMenu().getItem(0);
onNavigationItemSelected(menuItem);
menuItem.setChecked(true);
Ahora sí que tenemos un auténtico menú de navegación 😍

Además de la selección de un elemento, implementando un DrawerLayout.DrawerListener recibimos otros cuatro eventos asociados al menú:
public class HomeActivity extends AppCompatActivity
implements NavigationView.OnNavigationItemSelectedListener,
DrawerLayout.DrawerListener {
@Override
public void onDrawerSlide(@NonNull View view, float v) {
//cambio en la posición del drawer
}
@Override
public void onDrawerOpened(@NonNull View view) {
//el drawer se ha abierto completamente
Toast.makeText(this, getString(R.string.navigation_drawer_open),
Toast.LENGTH_SHORT).show();
}
@Override
public void onDrawerClosed(@NonNull View view) {
//el drawer se ha cerrado completamente
}
@Override
public void onDrawerStateChanged(int i) {
//cambio de estado, puede ser STATE_IDLE, STATE_DRAGGING or STATE_SETTLING
}
De nuevo, el listener debe ser asociado al componente que genera los eventos (DrawerLayout):
drawerLayout.addDrawerListener(this);
Así quedaron los métodos más relevantes de HomeActivity:
public class HomeActivity extends AppCompatActivity
implements NavigationView.OnNavigationItemSelectedListener,
DrawerLayout.DrawerListener {
private DrawerLayout drawerLayout;
private NavigationView navigationView;
private Toolbar toolbar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
setupToolbar();
setupDrawer();
}
private void setupToolbar() {
toolbar = findViewById(R.id.toolbar);
setSupportActionBar(findViewById(R.id.toolbar));
}
private void setupDrawer() {
drawerLayout = findViewById(R.id.drawer_layout);
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
this, drawerLayout, toolbar, R.string.navigation_drawer_open,
R.string.navigation_drawer_close);
drawerLayout.addDrawerListener(toggle);
toggle.syncState();
drawerLayout.addDrawerListener(this);
setupNavigationView();
}
private void setupNavigationView() {
navigationView = findViewById(R.id.navigation_view);
navigationView.setNavigationItemSelectedListener(this);
//set the default selected item
MenuItem menuItem = navigationView.getMenu().getItem(0);
onNavigationItemSelected(menuItem);
menuItem.setChecked(true);
setupHeader();
}
Integración con Navigation component
Hay una forma más sofisticada y elegante de configurar la navegación desde el menú lateral —y, en general, en toda la aplicación—: utilizando el framework Navigation de AndroidX. Su propósito es simplificar y estandarizar la gestión de la navegación a través de las pantallas de una aplicación. En la práctica, tendremos la navegación definida en un fichero XML editable con una herramienta visual. Con una API integraremos esa navegación con los componentes gráficos susceptibles de estar implicados con ella.
No profundizaré en Navigation; lo que veremos es cómo integrarlo en el proyecto de ejemplo, de tal modo que no tendremos que programar a mano la navegación que en la sección anterior escribimos en un NavigationView.OnNavigationItemSelectedListener.
Antes de nada, agregamos dos dependencias:
implementation 'androidx.navigation:navigation-fragment:2.5.3'
implementation 'androidx.navigation:navigation-ui:2.5.3'
Empecemos por la definición del grafo de navegación en el fichero /res/navigation/nav.xml:
<?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/nav_camera">
<fragment
android:id="@+id/nav_camera"
android:name="com.danielme.android.navigationdrawer.HomeContentFragment"
android:label="@string/menu_camera"
tools:layout="@layout/home_fragment">
<argument
android:name="text_id"
android:defaultValue="@string/menu_camera"
app:argType="reference" />
</fragment>
<fragment
android:id="@+id/nav_gallery"
android:name="com.danielme.android.navigationdrawer.HomeContentFragment"
android:label="@string/menu_gallery"
tools:layout="@layout/home_fragment">
<argument
android:name="text_id"
android:defaultValue="@string/menu_gallery"
app:argType="reference" />
</fragment>
<fragment
android:id="@+id/nav_tools"
android:name="com.danielme.android.navigationdrawer.HomeContentFragment"
android:label="@string/menu_tools"
tools:layout="@layout/home_fragment">
<argument
android:name="text_id"
android:defaultValue="@string/menu_tools"
app:argType="reference" />
</fragment>
<fragment
android:id="@+id/nav_share"
android:name="com.danielme.android.navigationdrawer.HomeContentFragment"
android:label="@string/menu_share"
tools:layout="@layout/home_fragment">
<argument
android:name="text_id"
android:defaultValue="@string/menu_share"
app:argType="reference" />
</fragment>
<fragment
android:id="@+id/nav_send"
android:name="com.danielme.android.navigationdrawer.HomeContentFragment"
android:label="@string/menu_send"
tools:layout="@layout/home_fragment">
<argument
android:name="text_id"
android:defaultValue="@string/menu_send"
app:argType="reference" />
</fragment>
</navigation>
Se indican las cinco pantallas (fragments) que tenemos y que se corresponden con las cinco entradas del menú. Para cada una, definimos los siguientes elementos:
- El identificador del destino de la navegación
- La clase del fragmento
- El layout del fragmento (dato en realidad informativo)
- El título de la pantalla que deberá mostrar la ActionBar
Recuerda que todas las pantallas son el mismo fragmento, solo cambia el texto que mostrará en su interior y que recibe como un argumento. Por este motivo, he utilizado bloques <argument> para establecer ese argumento.
En la línea seis tienes un detalle importante: startDestination indica la pantalla inicial de la navegación, la que queremos que se muestre al iniciarse HomeActivity.
En activity_home.xml añadimos un componente que permite a Navigation UI incrustar los fragmentos:
<LinearLayout
android:id="@+id/home_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" />
<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" />
</LinearLayout>
Como puedes ver, se trata de NavHostFragment, un fragmento que sirve de contenedor para la pantalla destino de una navegación. En su propiedad navGraph se estableció el fichero de navegación. También se activó el flag defaultNavHost para que este fragmento procese la navegación como consecuencia de la pulsación del botón Atrás.
Enlazamos cada item del menú de navegación con la pantalla que muestra. Tan sencillo como hacer que el identificador del item coincida con el identificador de la pantalla que deba mostrar.

Lastimosamente, las dificultades aparecen a la hora de establecer la integración en el código de la actividad. Esta es la nueva versión del método setupDrawer:
private void setupDrawer() {
drawerLayout = findViewById(R.id.drawer_layout);
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
NavController navController = navHostFragment.getNavController();
navigationView = findViewById(R.id.navigation_view);
NavigationUI.setupWithNavController(navigationView, navController);
appBarConfiguration =
new AppBarConfiguration.Builder(R.id.nav_camera, R.id.nav_gallery, R.id.nav_tools, R.id.nav_send, R.id.nav_share)
.setOpenableLayout(drawerLayout)
.build();
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
drawerLayout.addDrawerListener(this);
}
@Override
public boolean onSupportNavigateUp() {
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
return NavigationUI.navigateUp(navController, appBarConfiguration)
|| super.onSupportNavigateUp();
}
El código anterior lo he escrito según los ejemplos de la documentación oficial. Se nos ha complicado el asunto, pero no es tan difícil como parece. La clave reside en las líneas resaltadas.
En la línea ocho asociamos NavigationView con Navigation empleando una de las sobrecargas del método setupWithNavController de NavigationUI. Las líneas previas se limitan a obtener sus argumentos de entrada.
Lo anterior no es suficiente porque Navigation debe tomar el control de la Toolbar para establecer el título y mostrar el botón de navegación de su izquierda que siempre será el icono hamburguesa. Esto se consigue invocando a una sobrecarga del método setupActionBarWithNavController de NavigationUI. El objeto AppBarConfiguration que recibe se construye con la lista de pantallas que se consideran de primer nivel, en nuestro caso todas. Sin esa lista, la Toolbar mostrará el icono Atrás. También se requiere sobrescribir el método onSupportNavigateUp de la actividad (lo he copiado de la documentación) para que la pulsación del icono hamburguesa abra el menú.
Tras los cambios anteriores, el resultado es el mismo, solo que ahora gestionamos toda la configuración en el fichero nav.xml. Además de este beneficio, el nuevo código que requiere la actividad compensa el que nos ahorramos: la implementación del listener, la configuración de ActionBarDrawerToggle y el método setupNavigationView.
Nota. En el proyecto alojado en GitHub, el código relativo a Navigation está comentado para que la navegación sea realizada por OnNavigationItemSelectedListener.
Personalización de estilos
El menú, al igual que todos los componentes gráficos proporcionados por Google, se integra a las mil maravillas con los estilos y temas de Material Design \ Material Components. El objetivo es que nuestras aplicaciones tengan un aspecto armonioso y coherente, sin requerirse apenas configuración.
El elemento seleccionado aparece resaltado con un fondo o background de color grisáceo, quedando título e icono tintados con el color principal (colorPrimary) de la app definido en el fichero themes.xml. El fondo del menú toma el color de colorSurface y el icono y texto de los elementos no seleccionados son del color colorOnSurface.

En este enlace tienes los ejemplos oficiales de temas y estilos para Navigation Drawer.
Gracias a ciertas propiedades de NavigationView, algunos rasgos visuales del menú se pueden personalizar con facilidad. Por ejemplo, en itemIconTint e itemTextColor puedes establecer unos drawable con los colores que tendrán el icono y el texto en función del estado de la entrada del menú.
El siguiente selector (/res/drawable/drawer_selector.xml) define un color diferente para los dos estados básicos (seleccionado y no seleccionado):
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@android:color/holo_orange_light" android:state_checked="true" />
<item android:color="@android:color/holo_orange_dark" />
</selector>
Asociemos el selector anterior al NavigationView de modo que se aplique tanto al texto como al icono. Así, tendrán siempre el mismo color:
<com.google.android.material.navigation.NavigationView
android:id="@+id/navigation_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="@layout/nav_header"
app:itemIconTint="@drawable/drawer_selector"
app:itemTextColor="@drawable/drawer_selector"
app:menu="@menu/activity_home_navigation_drawer" />

Admito que la elección de colores resulta estrambótica. Quiero que sea llamativa para que se vea claro su empleo.
Como cabría esperar, el fondo de las entradas del menú también es configurable. Eso sí, exige algo más de trabajo porque lo normal es que queramos definirlo como una forma en un drawable.
Primero diseñamos un rectángulo de color sólido en el fichero /res/drawable/drawer_bakground_highlight.xml:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@android:color/holo_blue_bright" />
</shape>
A continuación, creamos un selector que muestre el drawable anterior cuando el elemento al que se aplique el selector esté seleccionado:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/drawer_bakground_highlight" android:state_checked="true" />
</selector>
Por último, aplicamos el selector al NavigationView con la propiedad itemBackground:
<com.google.android.material.navigation.NavigationView
android:id="@+id/navigation_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="@layout/nav_header"
app:itemBackground="@drawable/drawer_background_selector"
app:itemIconTint="@drawable/drawer_selector"
app:itemTextColor="@drawable/drawer_selector"
app:menu="@menu/activity_home_navigation_drawer" />

Resultado final
Eso fue todo. Como habrás apreciado, agregar a una app un menú de tipo Navigation Drawer con las características básicas que se esperan de él no resulta complicado. Te dejo un video para que veas como quedó el proyecto de ejemplo.
Código de ejemplo
El proyecto de ejemplo se encuentra en GitHub. Para más información sobre cómo utilizar GitHub, consulta este artículo.
Otros tutoriales Android
Toolbar, pestañas y ViewPager2 con AndroidX y Material Components
Menú inferior emergente con Bottom Sheet y Material Design
EditText con TextInputLayout y Material Components
Barra de menú inferior BottomNavigationView. Listener, badges y Navigation UI.
Muchas gracias por el demo, soy educador, y deseo crear una app para visualizar mi sitio web, no sé nada de programación de android studio ¿sabes como incluirle una webview? he visto que que la programación cambia de acuerdo al programador.
Hola, muchas gracias por el ejemplo, soy estudiante y acabo de terminar un módulo de Android. He implementado todo el código poro no consigo que que se ejecuten las acciones desde las opciones de este menú. Lo he repasado de arriba a abajo durante horas y nada.
Acabo de ejecutar el proyecto tal cual está en GitHub en el emulador para Android Api 30 con un Android Studio que acabo de instalar en Windows 10 y funciona correctamente. Definitivamente hay algún detalle que se te escapa…
Hola, sabes como abrir Facebook, Twitter, YouTube, etc. desde un icono del navigationdrawer? Me seria de mucha ayuda, gracias
Puedes abrir la web con el Intent que se muestra en https://danielme.com/tip-android-9-algunos-intents-utiles/