NavigationView: Navigation Drawer Con Material Design

¿Ya sabes que la nueva librería de soporte para el diseño en Android trae un nuevo elemento llamado NavigationView para crear un Navigation Drawer basado en Material Design?

Me imagino que aún estas implementando un Listview mas un adaptador para generar la lista de ítems dentro del DrawerLayout.

Incluso modificas el estilo y manipulas el contenido para añadir un header con imagen al navigation drawer de forma extensa.

Si quieres acelerar su implementación, entonces debes aprender a usar el NavigationView. Este hace que la construcción de un menú deslizante sea sencilla y aislada.

En este artículo veremos cómo añadir un navigation drawer, donde se variará el contenido principal basado en fragmentos. Además de ello añadiremos un header con un ImageView para construir un perfil. Adicionalmente setearemos los estilos y dimensiones necesarios para respetar el checklist del Navigation Drawer en el Material Design.

Material Design: Checklist Para Diseño Navigation Drawer

Descargar Proyecto Android Studio De “Janus Shop”

Si te preguntas que cuál será el ejemplo que verás en este tutorial, entonces el siguiente video ilustrativo te orientará:

Para desbloquear el link de descarga del código final, sigue estas instrucciones:

¿Cómo Añadir El NavigationView En Android?

En el apartado del NavigationView de la librería de soporte nos indica que el navigation drawer debe ser construido a partir de:

  • Un layout con un nodo DrawerLayout, el cual tendrá dos hijos. El contenido principal y un NavigationView.
  • Un layout para el header. Este elemento es opcional.
  • Un menú para inflar las opciones de la lista.

El primer elemento ya lo has creado antes, solo que en lugar de un navigation view, usábas una lista o creabas un recycler view:

<android.support.v4.widget.DrawerLayout 
    ...
    android:fitsSystemWindows="true">

    <!-- Contenido Principal -->

    <!-- Menú Deslizante -->
    <android.support.design.widget.NavigationView
        ...
        android:layout_gravity="start"
        android:fitsSystemWindows="true"
        app:headerLayout="@layout/nav_header"
        app:menu="@menu/nav_menu" />

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

La sintaxis anterior no es nada del otro mundo, solo recuerda tener en cuenta los siguientes puntos claves:

  • Usa el atributo fitsSystemWindows con el valor de true para ajustar el DrawerLayout y el NavigationView al padding de la status bar de android.
  • Asigna al atributo headerLayout el layout que ocupará el espacio del header, si es que usarás uno.
  • Asigna al atributo menu del NavigationView el menú con las opciones correspondientes.

El header puedes personalizarlo como desees. Por otro lado la estructura básica de un menú se vería así:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <group android:checkableBehavior="single">
        <item
            android:id="@+id/nav_home"
            android:checked="true"
            android:icon="@drawable/ic_home"
            android:title="@string/home_item" />
        <item
            android:id="@+id/nav_productos"
            android:icon="@drawable/ic_store"
            android:title="@string/productos_item" />
        <item
            android:id="@+id/nav_carrito"
            android:icon="@drawable/ic_shopping_cart"
            android:title="@string/compras_item" />
        ...
    </group>

</menu>

En este caso cada ítem tendría un identificador, un titulo y un icono asignado. Donde se agrupan por una etiqueta <group> para mantener el control sobre la selección.

También puedes usar un menú anidado para crear una división visible entre los elementos:

<!-- Otra sección -->
<item
    android:id="@+id/otra_section"
    android:title="@string/subtitulo">
    <menu>
        <item
            android:id="@+id/nav_otro_item"
            android:icon="@android:drawable/otro_icono"
            android:title="@string/otro_item" />
    </menu>
</item>

En cuanto a control de eventos e interacción con el sistema ten en cuenta que:

  • Debes añadir la Toolbar a tu aplicación Android para que el navigation drawer aparezcan sobre la Action Bar como dice la regla #1.
  • Añade una escucha OnNavigationItemSelectedListener a tu NavigationView para manejar las selecciones de los ítems.
  • Sobrepón la status bar al navigation drawer como dice la regla #1. Para ello añade a res/values-21 los siguientes atributos:
<style name="TuTema" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="android:windowDrawsSystemBarBackgrounds">true</item>
    <item name="android:statusBarColor">@android:color/transparent</item>
</style>

Crear Proyecto En Android Studio

Paso #1. Entra a Android Studio, dirígete a File > New Project y nombra al proyecto como “Janus Shop”. Al momento de añadir la actividad, seleccionar Blank Activity y confirma.

Paso #2. Abre el archivo build.gradle y añade las siguientes dependencias:

build.gradle

dependencies {
    ...
    compile 'com.android.support:appcompat-v7:22.2.0'
    compile 'com.android.support:design:22.2.0'
    compile 'de.hdodenhof:circleimageview:1.3.0'
}

Recuerda que la librería Circle Image View nos permite añadir imágenes circulares.

Paso #3. Agrega dentro del archivo res/values/strings.xml las siguientes cadenas:

strings.xml

<resources>
    <string name="app_name">Janus Shop</string>

    <string name="action_settings">Settings</string>

    <string name="home_item">Home</string>
    <string name="productos_item">Productos</string>
    <string name="compras_item">Carrito</string>
    <string name="ordenes_item">Ordenes</string>
    <string name="facturas_item">Facturas</string>
    <string name="log_out_item">Cerrar Sesión</string>
    <string name="configuracion_item">Configuración</string>
</resources>

Paso #4. El siguiente paso es definir los colores para el Material Design. Para ello crea un nuevo archivo xml dentro de res/values llamado colors.xml y añade los siguientes valores:

colors.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="window_background">#FFF5F5F5</color>

    <color name="primaryColor">#346D7D</color>
    <color name="primaryDarkColor">#1F515F</color>
    <color name="accentColor">#03A9F4</color>
</resources>

Paso #5. Abre tu archivo de estilos en res/values/styles.xml y añade los siguientes temas:

<resources>

    <style name="Theme.JanusShop" parent="Base.AppTheme"/>

    <style name="Base.AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="colorPrimary">@color/primaryColor</item>
        <item name="colorPrimaryDark">@color/primaryDarkColor</item>
        <item name="colorAccent">@color/accentColor</item>

        <item name="android:windowBackground">@color/window_background</item>
    </style>

</resources>

Adicionalmente configura la transparencia de la status bar en el archivo res/values-21/styles.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="Theme.JanusShop" parent="Base.AppTheme">
        <item name="android:windowDrawsSystemBarBackgrounds">true</item>
        <item name="android:statusBarColor">@android:color/transparent</item>
    </style>
</resources>

Paso #6. Ahora define las dimensiones dentro de tu archivo res/values/dimens.xml. En esta parte debes tener en cuenta las especificaciones de un navigation drawer.

Material Design: Especificaciones De Un Navigation Drawer

 

El código de abajo contiene las medidas necesarias para la aplicación Janus Shop.

dimens.xml

<resources>
    <dimen name="activity_horizontal_margin">16dp</dimen>
    <dimen name="activity_vertical_margin">16dp</dimen>

    <dimen name="header_left_padding">16dp</dimen>
    <dimen name="header_height">192dp</dimen>

    <dimen name="perfil_image_size">64dp</dimen>
</resources>

Paso #7. Descarga los recursos gráficos de nuestra aplicación. Al descomprimir el archivo .rar, debes copiar todas las carpetas en tu directorio main/res.

Crear Navigation Drawer Con Imagen De Cabecera

Paso #8. A continuación abre el archivo res/layout/activity_main.xml y añade la definición del navigation drawer que se encuentra en el siguiente código:

activity_main.xml

<android.support.v4.widget.DrawerLayout 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/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context=".MainActivity">

    <!-- Contenido Principal -->
    <include layout="@layout/main_content" />

    <!-- Menú Deslizante -->
    <android.support.design.widget.NavigationView
        android:id="@+id/nav_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/drawer_view" />

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

Paso #9. Crea un nuevo archivo dentro res/layout llamado nav_header.xml para definir el header del navigation drawer. La idea es crear un menú deslizante con una imagen de perfil circular.

Navigation Drawer Con Imagen De Perfil Circular

Copia la siguiente definición xml en el layout:

nav_header.xml

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

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="@dimen/header_height"
    android:background="@drawable/material_background3"
    android:gravity="bottom"
    android:orientation="vertical"
    android:padding="@dimen/header_left_padding"
    android:theme="@style/ThemeOverlay.AppCompat.Dark">

    <!-- Imagen de perfil -->
    <de.hdodenhof.circleimageview.CircleImageView
        android:id="@+id/circle_image"
        android:layout_width="@dimen/perfil_image_size"
        android:layout_height="@dimen/perfil_image_size"
        android:layout_marginBottom="16dp"
        android:scaleType="centerCrop"
        android:src="@drawable/perfil2"
        app:border_color="@android:color/white"
        app:border_width="1dp" />

    <!-- Nombre de Usuario -->
    <TextView
        android:id="@+id/username"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Juana Maria Perez"
        android:textAppearance="@style/TextAppearance.AppCompat.Body1"
        android:textStyle="bold" />

    <!-- Correo de la cuenta -->
    <TextView
        android:id="@+id/email"
        style="@style/Widget.AppCompat.Spinner"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="juanaperez01@gmail.com"
        android:textAppearance="@style/TextAppearance.AppCompat.Body1" />

</LinearLayout>

Paso #10. Ahora crea un layout llamado main_content.xml con el código de abajo. Este diseño será para el contenido principal de la actividad, donde se decidió añadir la Toolbar más un RelativeLayout que contendrá el espacio para actualizar el contenido.

main_content.xml

<LinearLayout 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:orientation="vertical">

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

    <RelativeLayout
        android:id="@+id/main_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

Paso #11. El siguiente paso es crear el menú para el navigation drawer. Ve a res/menu y añade un archivo llamado nav_menu.xml con la siguiente definición:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <group android:checkableBehavior="single">
        <item
            android:id="@+id/nav_home"
            android:checked="true"
            android:icon="@drawable/ic_home"
            android:title="@string/home_item" />
        <item
            android:id="@+id/nav_productos"
            android:icon="@drawable/ic_store"
            android:title="@string/productos_item" />
        <item
            android:id="@+id/nav_carrito"
            android:icon="@drawable/ic_shopping_cart"
            android:title="@string/compras_item" />
        <item
            android:id="@+id/nav_ordenes"
            android:icon="@drawable/ic_truck"
            android:title="@string/ordenes_item" />
        <item
            android:id="@+id/nav_facturas"
            android:icon="@drawable/ic_file_document"
            android:title="@string/facturas_item" />

        <!-- Sección de configuración -->
        <item
            android:id="@+id/configuration_section"
            android:title="@string/configuracion_item">
            <menu>
                <item
                    android:id="@+id/nav_log_out"
                    android:icon="@android:drawable/ic_lock_power_off"
                    android:title="@string/log_out_item" />
            </menu>
        </item>
    </group>

</menu>

Con las definiciones anteriores, nuestro navigation drawer se vería como sigue:

NavigationView: Navigation Drawer Con Material Design

¿Quieres ahorrar montones de horas en el desarrollo de una app con Nav Drawer? Si es así, te recomiendo descargar Universal – Full Multi-Purpose Android App de CodeCanyon.

Paso #12. Lo siguiente es modificar el archivo MainActivity.java para implementar el navigation drawer. Añade el siguiente código:

MainActivity.java

import android.os.Bundle;
import android.support.design.widget.NavigationView;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
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;

public class MainActivity extends AppCompatActivity {
    /**
     * Instancia del drawer
     */
    private DrawerLayout drawerLayout;

    /**
     * Titulo inicial del drawer
     */
    private String drawerTitle;

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

        setToolbar(); // Setear Toolbar como action bar

        drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
        if (navigationView != null) {
            // Añadir carácteristicas
        }

        drawerTitle = getResources().getString(R.string.home_item);
        if (savedInstanceState == null) {
            // Seleccionar item
        }

    }

    private void setToolbar() {
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        final ActionBar ab = getSupportActionBar();
        if (ab != null) {
            // Poner ícono del drawer toggle
            ab.setHomeAsUpIndicator(R.drawable.ic_menu);
            ab.setDisplayHomeAsUpEnabled(true);
        }

    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        if (!drawerLayout.isDrawerOpen(GravityCompat.START)) {
            getMenuInflater().inflate(R.menu.main, menu);
            return true;
        }
        return super.onCreateOptionsMenu(menu);
    }

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

}

Si corres la aplicación podrás visualizar el navigation drawer, pero no se cerrará automáticamente, ni se actualizará el contenido.

Para ello debemos extender el comportamiento con fragmentos y algunos métodos especiales del DrawerLayout.

Usar Fragmentos Con El Navigation Drawer

Paso #13. Crea una nueva clase llamada PlaceHolderFragment.java para representar el fragmento que se añadirá al contenido principal. Copia y pega el código de abajo.

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

/**
 * Fragmento para el contenido principal
 */
public class PlaceholderFragment extends Fragment {
    /**
     * Este argumento del fragmento representa el título de cada
     * sección
     */
    public static final String ARG_SECTION_TITLE = "section_number";


    /**
     * Crea una instancia prefabricada de {@link PlaceholderFragment}
     *
     * @param sectionTitle Título usado en el contenido
     * @return Instancia dle fragmento
     */
    public static PlaceholderFragment newInstance(String sectionTitle) {
        PlaceholderFragment fragment = new PlaceholderFragment();
        Bundle args = new Bundle();
        args.putString(ARG_SECTION_TITLE, sectionTitle);
        fragment.setArguments(args);
        return fragment;
    }

    public PlaceholderFragment() {
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.section_fragment, container, false);

        // Ubicar argumento en el text view de section_fragment.xml
        String title = getArguments().getString(ARG_SECTION_TITLE);
        TextView titulo = (TextView) view.findViewById(R.id.title);
        titulo.setText(title);
        return view;
    }

}

Como ves, el fragmento tiene un argumento que representa el título que se añadirá al contenido mediante un TextView.

Paso #14. Ve a res/layout y crea un nuevo fichero llamado section_fragment.xml con la definición de abajo. Este será el layout para nuestro fragmento.

section_fragment.xml

<?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:gravity="center_vertical"
    android:orientation="vertical">

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="Large Text"
        android:textAppearance="?android:attr/textAppearanceSmall"
        android:textSize="50sp"
        android:textStyle="bold" />

</LinearLayout>

Paso #15. Finalmente dentro de MainActivity.java añade las modificaciones que se presentan abajo.

Se usará el método setupDrawerContent() para añadir la escucha al atributo navigationView. Lo que permitirá actuar según el ítem selecionado del navigation drawer con el método onNavigationItemSelected().

Lo ideal es marcar el ítem actual en el navigation drawer con el método setChecked().

El nuevo método selectItem() tiene como propósito para crear fragmentos dinámicamente basado en el ítem seleccionado.

MainActivity.java

import android.os.Bundle;
import android.support.design.widget.NavigationView;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
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;

public class MainActivity extends AppCompatActivity {
    /**
     * Instancia del drawer
     */
    private DrawerLayout drawerLayout;

    /**
     * Titulo inicial del drawer
     */
    private String drawerTitle;

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

        setToolbar(); // Setear Toolbar como action bar

        drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
        if (navigationView != null) {
            setupDrawerContent(navigationView);
        }

        drawerTitle = getResources().getString(R.string.home_item);
        if (savedInstanceState == null) {
            selectItem(drawerTitle);
        }

    }

    private void setToolbar() {
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        final ActionBar ab = getSupportActionBar();
        if (ab != null) {
            // Poner ícono del drawer toggle
            ab.setHomeAsUpIndicator(R.drawable.ic_menu);
            ab.setDisplayHomeAsUpEnabled(true);
        }

    }

    private void setupDrawerContent(NavigationView navigationView) {
        navigationView.setNavigationItemSelectedListener(
                new NavigationView.OnNavigationItemSelectedListener() {

                    @Override
                    public boolean onNavigationItemSelected(MenuItem menuItem) {
                        // Marcar item presionado
                        menuItem.setChecked(true);
                        // Crear nuevo fragmento
                        String title = menuItem.getTitle().toString();
                        selectItem(title);
                        return true;
                    }
                }
        );
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        if (!drawerLayout.isDrawerOpen(GravityCompat.START)) {
            getMenuInflater().inflate(R.menu.main, menu);
            return true;
        }
        return super.onCreateOptionsMenu(menu);
    }

    @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 selectItem(String title) {
        // Enviar título como arguemento del fragmento
        Bundle args = new Bundle();
        args.putString(PlaceholderFragment.ARG_SECTION_TITLE, title);

        Fragment fragment = PlaceholderFragment.newInstance(title);
        fragment.setArguments(args);
        FragmentManager fragmentManager = getSupportFragmentManager();
        fragmentManager
                .beginTransaction()
                .replace(R.id.main_content, fragment)
                .commit();

        drawerLayout.closeDrawers(); // Cerrar drawer

        setTitle(title); // Setear título actual

    }


}

Para manejar el flujo del navigation drawer es importante remarcar las siguientes implementaciones:

  • No olvides controlar la apertura del drawer con el método DrawerLayout.openDrawer() cuando es seleccionado el toggle o up button. Este recibe una bandera GravityCompat.START para indicar que se desplegará desde el lado izquierdo.
  • Similarmente cierra el drawer con DrawerLayout.closeDrawers() justo en el momento que se procesa la selección del ítem.
  • También puedes comprobar el estado del navigation drawer con el método DrawerLayout.isDrawerOpen().

Conclusión

Definitivamente el Navigation View es un componente que reduce nuestro trabajo para obtener un navigation drawer visualmente atractivo y en regla con el estilo Material Design.

Vimos lo fácil que es poblar el menú horizontal y como procesar los eventos de cada ítem. También el aislamiento del header para personalizar su contenido ajustado a una medida razonable.

Es excelente que Google haya estandarizado este elemento para evitar perder tiempo en personalizaciones y código innecesario.

Solo tienes que preocuparte en el contenido que incluirás en los fragmentos, como recycler views con swipe refresh layouts, actividades de preferencias, fragmentos de detalle, etc.

Ahora el paso a seguir es entender como crear pestañas con la nueva librería de soporte.

Icono de la aplicación