X

Navigation Drawer – Android Design Support Library

El Navigation Drawer es una gran forma de organizar tu app ya que permite disponer de todo el espacio en pantalla además de ser conocido por los usuarios al utilizarse en la gran mayoría de las apps, entre ellas las de Google.
Éste último ha publicado durante la I/O 2015 una nueva librería, Android Design Support Library, que permite crear un Navigation Drawer fácilmente entre otros elementos, como pueden ser los botones de acción flotantes (floating action buttons), pestañas (tabs), etc.
Todo lo que has se va a mostrar en esta entrada también lo puedes seguir ver en el siguiente vídeo de abajo.
palette

MATERIAL DESIGN

Especificaciones de un Navigation Drawer con diseño Material Design

PROYECTO EN GITHUB

Puedes descargar el proyecto con todo el código en mi GitHub

Primero vamos a ver el archivo MainActivity.java al completo para después ir desguazando cada parte del mismo.

import android.content.Intent;
import android.os.Bundle;
import android.support.design.widget.NavigationView;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    DrawerLayout drawerLayout;
    Toolbar toolbar;
    ActionBar actionBar;
    TextView textView;

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

        toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        actionBar = getSupportActionBar();
        actionBar.setHomeAsUpIndicator(R.drawable.ic_menu_white_24dp);
        actionBar.setDisplayHomeAsUpEnabled(true);

        drawerLayout = (DrawerLayout) findViewById(R.id.navigation_drawer_layout);

        NavigationView navigationView = (NavigationView) findViewById(R.id.navigation_view);
        if (navigationView != null) {
            setupNavigationDrawerContent(navigationView);
        }

        setupNavigationDrawerContent(navigationView);

    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                drawerLayout.openDrawer(GravityCompat.START);
                return true;
        }
        return super.onOptionsItemSelected(item);
    }

    private void setupNavigationDrawerContent(NavigationView navigationView) {
        navigationView.setNavigationItemSelectedListener(
                new NavigationView.OnNavigationItemSelectedListener() {
                    @Override
                    public boolean onNavigationItemSelected(MenuItem menuItem) {
                        textView = (TextView) findViewById(R.id.textView);
                        switch (menuItem.getItemId()) {
                            case R.id.item_navigation_drawer_inbox:
                                menuItem.setChecked(true);
                                textView.setText(menuItem.getTitle());
                                drawerLayout.closeDrawer(GravityCompat.START);
                                return true;
                            case R.id.item_navigation_drawer_starred:
                                menuItem.setChecked(true);
                                textView.setText(menuItem.getTitle());
                                drawerLayout.closeDrawer(GravityCompat.START);
                                return true;
                            case R.id.item_navigation_drawer_sent_mail:
                                menuItem.setChecked(true);
                                textView.setText(menuItem.getTitle());
                                drawerLayout.closeDrawer(GravityCompat.START);
                                return true;
                            case R.id.item_navigation_drawer_drafts:
                                menuItem.setChecked(true);
                                textView.setText(menuItem.getTitle());
                                drawerLayout.closeDrawer(GravityCompat.START);
                                return true;
                            case R.id.item_navigation_drawer_settings:
                                menuItem.setChecked(true);
                                textView.setText(menuItem.getTitle());
                                Toast.makeText(MainActivity.this, "Launching " + menuItem.getTitle().toString(), Toast.LENGTH_SHORT).show();
                                drawerLayout.closeDrawer(GravityCompat.START);
                                Intent intent = new Intent(MainActivity.this, SettingsActivity.class);
                                startActivity(intent);
                                return true;
                            case R.id.item_navigation_drawer_help_and_feedback:
                                menuItem.setChecked(true);
                                Toast.makeText(MainActivity.this, menuItem.getTitle().toString(), Toast.LENGTH_SHORT).show();
                                drawerLayout.closeDrawer(GravityCompat.START);
                                return true;
                        }
                        return true;
                    }
                });
    }
}

Lo primero que vemos arriba son todos los elementos que se han importado, hay que tener cuidado ya que algunas veces cuando añades una, por ejemplo, Toolbar, en vez de importarte import android.support.v7.widget.Toolbar; te importa otro elemento diferente. Esto ocurre con varias de ellos.

Figura 1. Elementos del Navigation Drawer.

Lo siguiente que hacemos es declarar los elementos necesarios dentro de nuestra case principal, pero fuera del método onCreate:

DrawerLayout drawerLayout;
    Toolbar toolbar;
    ActionBar actionBar;
    TextView textView;

En el método onCreate damos soporte a la Toolbar que más adelante añadiremos al layout activity_main.xml, aparte de mostrar el icono característico de las tres rayas paralelas en la ActionBarToolbar (como queráis llamar a la barra superior de la app).

toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        actionBar = getSupportActionBar();
        actionBar.setHomeAsUpIndicator(R.drawable.ic_menu_white_24dp);
        actionBar.setDisplayHomeAsUpEnabled(true);

El código restante dentro del método onCreate son los engranajes para hacer funcionar al navigation drawer mediante el nuevo View llamado NavigationView. Básicamente hacemos referencia al mismo, y para que quede más limpio el código, creamos un método llamado setupNavigationDrawerContent, en el cual le pasamos la instancia que hemos creado navigationView. El método lo colocaremos fuera del método onCreate siendo el siguiente:

private void setupNavigationDrawerContent(NavigationView navigationView) {
        navigationView.setNavigationItemSelectedListener(
                new NavigationView.OnNavigationItemSelectedListener() {
                    @Override
                    public boolean onNavigationItemSelected(MenuItem menuItem) {
                        textView = (TextView) findViewById(R.id.textView);
                        switch (menuItem.getItemId()) {
                            case R.id.item_navigation_drawer_inbox:
                                menuItem.setChecked(true);
                                textView.setText(menuItem.getTitle());
                                drawerLayout.closeDrawer(GravityCompat.START);
                                return true;
                            case R.id.item_navigation_drawer_starred:
                                menuItem.setChecked(true);
                                textView.setText(menuItem.getTitle());
                                drawerLayout.closeDrawer(GravityCompat.START);
                                return true;
                            case R.id.item_navigation_drawer_sent_mail:
                                menuItem.setChecked(true);
                                textView.setText(menuItem.getTitle());
                                drawerLayout.closeDrawer(GravityCompat.START);
                                return true;
                            case R.id.item_navigation_drawer_drafts:
                                menuItem.setChecked(true);
                                textView.setText(menuItem.getTitle());
                                drawerLayout.closeDrawer(GravityCompat.START);
                                return true;
                            case R.id.item_navigation_drawer_settings:
                                menuItem.setChecked(true);
                                textView.setText(menuItem.getTitle());
                                Toast.makeText(MainActivity.this, "Launching " + menuItem.getTitle().toString(), Toast.LENGTH_SHORT).show();
                                drawerLayout.closeDrawer(GravityCompat.START);
                                Intent intent = new Intent(MainActivity.this, SettingsActivity.class);
                                startActivity(intent);
                                return true;
                            case R.id.item_navigation_drawer_help_and_feedback:
                                menuItem.setChecked(true);
                                Toast.makeText(MainActivity.this, menuItem.getTitle().toString(), Toast.LENGTH_SHORT).show();
                                drawerLayout.closeDrawer(GravityCompat.START);
                                return true;
                        }
                        return true;
                    }
                });
    }

Cómo se puede observar en Figura 1, básicamente estamos haciendo referencia a todos los elementos que habrá en el Nacivigation Drawer. Así, con el switch, cuando el usuario seleccione uno de los elementos, ocurrirá lo siguiente para cada uno de ellos:

  1. Se marcará como seleccionado el ítem en cuestión gracias a menuItem.setChecked(true).
  2. El TextView que se encuentra en mitad de la pantalla cambiará de nombre, obteniendo el mismo del título del ítem presionado, textView.setText(menuItem.getTitle());.
  3. Se cerrará el navigation drawer mediante drawerLayout.closeDrawer(GravityCompat.START);.
  • En el caso del elemento Settings, también se va a iniciar otra actividad, como se puede ver en las líneas destacadas:
case R.id.item_navigation_drawer_settings:
     menuItem.setChecked(true);
     textView.setText(menuItem.getTitle());
     Toast.makeText(MainActivity.this, "Launching " + menuItem.getTitle().toString(), Toast.LENGTH_SHORT).show();
     drawerLayout.closeDrawer(GravityCompat.START);
     Intent intent = new Intent(MainActivity.this, SettingsActivity.class);
     startActivity(intent);
     return true;

Y para finalizar, el código siguiente es para abrir el navigation drawer si se presiona sobre el botón de las tres rayas en la Toolbar, mediante el siguiente método:

@Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                drawerLayout.openDrawer(GravityCompat.START);
                return true;
        }
        return super.onOptionsItemSelected(item);
    }

Layout activity_main.xml

Cómo hemos hecho antes, vamos a ver el código al completo y lo vamos desguazando:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/navigation_drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="@bool/fitsSystemWindows">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="@dimen/status_bar_height"
            android:background="?colorPrimary"/>

    </LinearLayout>

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/status_bar_height">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?actionBarSize"
            android:background="?attr/colorPrimary"
            android:elevation="4dp"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
            app:theme="@style/ToolbarTheme" />

        <TextView
            android:id="@+id/textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:text="Inbox"
            android:textAppearance="@style/TextAppearance.AppCompat.Display1"
            android:textColor="@color/md_text" />

    </FrameLayout>

    <android.support.design.widget.NavigationView
        android:id="@+id/navigation_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:fitsSystemWindows="@bool/fitsSystemWindows"
        app:headerLayout="@layout/navigation_drawer_header"
        app:menu="@menu/navigation_drawer_menu"
        app:theme="@style/NavigationViewTheme" />

</android.support.v4.widget.DrawerLayout>

El contenedor principal es, evidentemente, un DrawerLayout, con un atributo que hay que mencionar por ser muy importante, que es el android:fitsSystemWindows="true", para evitar que los elementos de la pantalla superpongan las barras del sistema. El DrawerLayout se encarga de poder tener un Navigation Drawer además de contener el resto de elementos de la actividad principal.

Dentro del DrawerLayout se puede ver que hay un FrameLayout que contiene una Toolbar, un TextView, y el elemento estrella, un NavigationView. En este es en el que nos tenemos que centrar en esta entrada, para los otros dos visita las páginas correspondientes o echa un vistazo al vídeo en el que explicaré más cosas que aquí.

<android.support.design.widget.NavigationView
    android:id="@+id/navigation_view"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:layout_gravity="start"
    android:fitsSystemWindows="@bool/fitsSystemWindows"
    app:headerLayout="@layout/navigation_drawer_header"
    app:menu="@menu/navigation_drawer_menu"
    app:theme="@style/NavigationViewTheme" />

Vamos a ver cada uno de los nuevos atributos del NavigationView:

  1. El primer atributo que quiero destacar es el android:fitsSystemWindows="@bool/fitsSystemWindows", ya que como veis no le estamos dando un valor truefalse, le estamos indicando que le daremos el valor @bool/fitsSystemWindows, este valor lo determinaremos nosotros en los archivos attrs.xml que haremos para cada versión, ya que casi todas las versiones necesita que sea True, excepto para KitKat es necesario que sea False. También verás en la Toolbar o en el FrameLayout que también utilizamos esto, además de una altura que variará en el FrameLayout y en el margen superior de otro FrameLayout. Como he dicho antes, visita el vídeo si no te queda claro el por qué lo he hecho.
  2. El atributo relevante es app:headerLayout="@layout/navigation_drawer_header" se encarga de permitirte que puedas añadir un header (la parte superior clásica con la imagen, el avatar, etc) al navigation drawer, este header lo tienes que crear en otro layout que verás más adelante y que se llamará navigation_drawer_header.xml.
  3. El atributo app:menu="@menu/navigation_drawer_menu" es para indicarle los elementos que tendrá el navigation drawer. Es aquí donde se encuentra la magia ya que te evitas tener que hacer un RecyclerView o un ListView además de tener que personalizar cada item para que cumpla las especificaciones de material design.
  4. El último atributo app:theme="@style/NavigationViewTheme" lo he añadido yo personalmente ya que, por ahora, los textos están utilizando por defecto la fuente Roboto Regular, y si has visitado las especificaciones de material design para el navigation drawer, verás que debería ser Roboto Medium. Con este método lo arreglamos después en nuestro archivo styles.xml. Es probable que esto más adelante no haga falta ya que lo arregle Google por defecto.

Archivos de estilos styles.xml para todas las versiones

Aquí básicamente tenemos los elementos necesarios para dar los colores y el formato necesario para todas las versiones. El arreglo que mencioné anteriormente de que es necesario que el Navigation Drawer muestre los elementos con la letra Roboto Medium en vez de Roboto Regular es el siguiente:

<style name="NavigationViewTheme" parent="AppTheme">
        <item name="textAppearanceListItem">@style/TextAppearance.AppCompat.Body2</item>
    </style>

Como se puede ver, en la v19 le indicamos que la status bar sea translucida y en la v21 que sea transparente además del otro atributo para que en el navigation drawer se muestre el header debajo de la status bar. Como veis, aunque le decimos que la barra tiene que ser transparente en la v21, la librería ya se encarga de añadir el translucido cuando el navigation drawer se abre.

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

    <style name="AppTheme" parent="Base.AppTheme">

    </style>

    <style name="Base.AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="colorPrimary">@color/md_indigo_500</item>
        <item name="colorPrimaryDark">@color/md_indigo_700</item>
        <item name="colorAccent">@color/md_pink_500</item>
        <item name="colorControlHighlight">@color/md_indigo_500_25</item>
        <item name="android:windowBackground">@color/md_white_1000</item>
    </style>

    <style name="ToolbarTheme" parent="AppTheme">
        <item name="android:textColorPrimary">@android:color/white</item>
        <item name="android:textColorSecondary">@android:color/white</item>
    </style>

    <style name="NavigationViewTheme" parent="AppTheme">
        <item name="textAppearanceListItem">@style/TextAppearance.AppCompat.Body2</item>
    </style>

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

    <style name="AppTheme" parent="Base.AppTheme">
        <item name="android:windowTranslucentStatus">true</item>
    </style>

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

    <style name="AppTheme" parent="Base.AppTheme">
        <item name="android:windowDrawsSystemBarBackgrounds">true</item>
        <item name="android:statusBarColor">@android:color/transparent</item>
    </style>

</resources>

Colores mediante el archivo colors.xml

Los colores utilizados los puedes añadir tanto de mi GitHub como visitando esta página sobre Color en Material Design. Simplemente crea un archivo colors.xml en app/res/values/colors.xml.

Los strings son los que se crean por defecto al ir creando cada uno de los elementos.

<resources>
    <string name="app_name">Navigation Drawer</string>
    <string name="hello_world">Hello world!</string>
    <string name="action_settings">Settings</string>
    <string name="title_activity_settings">Settings</string>
</resources>

Archivos de atributos attrs.xml para todas las versiones

Estos archivos son para arreglar lo que hemos ido mencionando a lo largo de la entrada:

  1. Para que Navigation Drawer se vea bien en Kitkat
  2. Para que el avatar del header se vea bien en versiones de Android con barra translucida y negra.
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <bool name="fitsSystemWindows">true</bool>
    <dimen name="navigation_drawer_header_margin">16dp</dimen>
    <dimen name="status_bar_height">0dp</dimen>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <bool name="fitsSystemWindows">false</bool>
    <dimen name="navigation_drawer_header_margin">41dp</dimen>
    <dimen name="status_bar_height">25dp</dimen>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <bool name="fitsSystemWindows">true</bool>
    <dimen name="navigation_drawer_header_margin">41dp</dimen>
    <dimen name="status_bar_height">0dp</dimen>
</resources>

Drawables utilizados

Los drawables utilizados los puedes encontrar en la siguiente carpeta de GitHub.

Además, recomiendo visitar las siguientes páginas para obtener más recursos:

Segunda Actividad

Cómo se ha dicho, el elemento del navigation drawer Settings, inicia una segunda actividad, por lo que es necesario crearla, aunque no tiene nada de especial, simplemente os dejo aquí los cambios que he añadido al SettingsActivity.java y al activity_settings.xml, ya que el archivo menu_settings no ha sido modificado.

import android.os.Build;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.Toolbar;
import android.util.TypedValue;
import android.view.Menu;
import android.view.MenuItem;

public class SettingsActivity extends AppCompatActivity {

    Toolbar toolbar;

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

        toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);

        TypedValue typedValueColorPrimaryDark = new TypedValue();
        SettingsActivity.this.getTheme().resolveAttribute(R.attr.colorPrimaryDark, typedValueColorPrimaryDark, true);
        final int colorPrimaryDark = typedValueColorPrimaryDark.data;
        if (Build.VERSION.SDK_INT >= 21) {
            getWindow().setStatusBarColor(colorPrimaryDark);
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_inbox, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}
<FrameLayout 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="match_parent">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?actionBarSize"
        android:background="?attr/colorPrimary"
        android:elevation="4dp"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
        app:theme="@style/ToolbarTheme" />

    <TextView
        android:textAppearance="@style/TextAppearance.AppCompat.Display1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="Settings"
        android:textColor="@color/md_text" />

</FrameLayout>

Y ya está todo listo, tu navigation drawer debería funcionar correctamente si lo ejecutas.

Puedes ver más información sobre Navigation Drawer en la página oficial de Google en inglés pulsando aquí.

Los comentarios de Disqus están cargando....