En este nuevo artículo del curso vamos a tratar otro de los componentes relacionados con la capa de presentación de nuestras aplicaciones, el menú lateral deslizante, o Navigation Drawer. Este menú es el que aparece en muchas aplicaciones al deslizar el dedo desde el borde izquierdo de la pantalla hacia el lado opuesto (también puede aparecer en el lado derecho, pero es menos frecuente).
El navigation drawer está disponible como parte de la librería de compatibilidad android-support-v4. Si estamos trabajando con Android Studio, en la mayoría de los casos no tendremos que añadir esta librería al proyecto de forma explícita ya que muy probablemente tengamos ya añadida la librería appcompat-v7 (se incluye por defecto en los proyectos de Android Studio), que depende de la primera.
En este artículo vamos a crear un menú lateral que cumpla todo lo posible las directrices marcadas por las nuevas guías de diseño de Material Design. Dicha guía establece que el menú de navegación lateral debe pasar por encima de la action bar y por debajo de la status bar (que será translúcida en Android 5.0 y posteriores).
Veremos cómo conseguir este efecto para versiones de Android a partir de la 5.0 Lollipop (API 21), mientras que en versiones anteriores la status bar permanecerá de color negro.
Vamos a empezar a crear nuestra aplicación, y comenzaremos como siempre creando la interfaz de usuario. Para añadir el navigation drawer a una actividad debemos hacer que el elemento raíz del layout XML sea del tipo <android.support.v4.widget.DrawerLayout>. Y dentro de este elemento colocaremos únicamente 2 componentes principales (en el orden indicado):
- El layout real de la actividad.
- El layout del menú lateral, que entre otras cosas hará las veces de contenedor de las distintas opciones del menú lateral.
El primero de estos elementos lo añadiremos por ahora en forma de <include> y después volveremos sobre él. Para el segundo vamos a utilizar otro de los nuevos componentes incluidos con la nueva librería de diseño de Android (Design Support Library). El componente en cuestión es el llamado NavigationView, que nos ayudará bastante en la construcción del layout del menú lateral.
En mi caso de ejemplo quedaría como sigue (/res/layout/activity_main.xml):
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" tools:context=".MainActivity"> <!-- Layout real de la actividad --> <include layout="@layout/content_layout" /> <!-- Layout del menú lateral (Navigation View) --> <android.support.design.widget.NavigationView android:id="@+id/navview" android:layout_width="wrap_content" android:layout_height="match_parent" android:fitsSystemWindows="true" android:layout_gravity="start" app:headerLayout="@layout/header_navview" app:menu="@menu/menu_navview" /> </android.support.v4.widget.DrawerLayout>
Varios detalles a destacar en el código anterior. En cuanto al DrawerLayout es importante asignar a true la propiedad android:fitsSystemWindows, que ayudará a conseguir el efecto indicado de deslizamiento del menú por debajo de la status bar.
Respecto al NavigationView, vemos como también asignamos su propiedad fitsSystemWindows de forma análoga al DrawerLayout. Posteriormente asignamos las propiedades quizá más relevantes del nuevo componente:
- La primera de ellas, android:layout_gravity, determina el lado de la pantalla por el que aparecerá el menú deslizante («start» para que aparezca por la izquierda, o «end» por la derecha).
- Con app:headerLayout (opcional) asignamos al menú lateral el layout XML de su cabecera, es decir, de la zona que queda por encima de la lista de opciones del menú.
- Por último, con app:menu, indicamos el recurso de menú que mostraremos en el navigation drawer. El componente NavigationView utiliza el sistema de menús habitual de Android, por lo que este menú podemos definirlo de forma análoga a como ya lo hicimos por ejemplo en el artículo dedicado a la action bar para definir el menú de overflow, aunque más adelante mostraremos alguna peculiaridad.
Veamos a continuación la definición del layout XML utilizado como cabecera del navigation drawer (/res/layout/header_navview.xml). En la cabecera del menú lateral suele incluirse en muchas aplicaciones información sobre el usuario logueado, y opciones para cambiar de usuario si esto fuera posible. En mi caso, para no complicar el ejemplo, voy a utilizar tan sólo una imagen de fondo y una etiqueta de texto, aunque aclarar que este layout puede ser todo lo complejo que sea necesario.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:layout_width="match_parent" android:layout_height="200dp" android:src="@drawable/navheader" android:scaleType="centerCrop" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/usuario" android:textAppearance="@style/TextAppearance.AppCompat.Large.Inverse" android:textStyle="bold" android:layout_gravity="bottom" android:layout_marginBottom="10dp" android:layout_marginLeft="10dp" /> </FrameLayout>
Respecto al menú a utilizar, ya dijimos que se definirá utilizando la sintaxis habitual de los recursos de tipo menú. En mi caso añadiré tres secciones principales y dos opciones adicionales:
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <group android:checkableBehavior="single"> <item android:id="@+id/menu_seccion_1" android:icon="@drawable/ic_menu" android:title="@string/seccion_1"/> <item android:id="@+id/menu_seccion_2" android:icon="@drawable/ic_menu" android:title="@string/seccion_2"/> <item android:id="@+id/menu_seccion_3" android:icon="@drawable/ic_menu" android:title="@string/seccion_3"/> </group> <item android:id="@+id/navigation_subheader" android:title="@string/otras_opciones"> <menu> <item android:id="@+id/menu_opcion_1" android:icon="@drawable/ic_menu" android:title="@string/opcion_1"/> <item android:id="@+id/menu_opcion_2" android:icon="@drawable/ic_menu" android:title="@string/opcion_2"/> </menu> </item> </menu>
Comentemos algunos detalles de la definición anterior. En primer lugar vemos que las tres secciones principales se engloban en un elemento <group> al que hemos asignado su propiedad checkableBehavior con valor «single«. Con esto indicamos que sólo pueda seleccionarse una de estas tres opciones al mismo tiempo (más tarde veremos como resaltar la opción seleccionada). A continuación se añaden dos opciones más dentro de un submenú al que asignamos su título con la propiedad android:title. Este título del submenú aparecerá en forma de cabecera de sección dentro del menú, incluyendo incluso una linea de división tras las tres opciones anteriores. Adicionalmente, en todas las opciones de menú indicamos su id (android:id), su icono (android:icon) y su título (android:title). En mi caso he utilizado por simplicidad el mismo icono en todas las opciones, pero por supuesto pueden ser distintos.
En una imagen veremos mejor el resultado:
Como nota importante indicar que las opciones incluidas dentro de un submenú no es posible resaltarlas en la interfaz como sí ocurre con las opciones anteriores incluidas dentro del elemento group. Es posible que en futuras versiones de la librería de diseño se habilite esta posibilidad.
Veamos a continuación el layout de la actividad principal. Para este ejemplo utilizaré un layout muy similar a los ya mostados en los artículos anteriores sobre la action bar. Contendrá tan sólo un Toolbar, que estableceremos como action bar en el onCreate() de la actividad, y un FrameLayout que nos servirá como contenedor de los fragment que contendrán cada sección del menú lateral. Dicho de otra forma, cada sección principal de la aplicación la implementaremos mediante un fragment independiente, y al pulsar cada opción del menú lateral, instanciaremos el fragment de su tipo correspondiente y lo colocaremos en el lugar del FrameLayout indicado. Veamos cómo quedaría (/res/layout/content_layout.xml):
<LinearLayout 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" android:orientation="vertical" android:clickable="true" tools:context=".MainActivity" tools:showIn="@layout/activity_main"> <!-- Toolbar --> <android.support.v7.widget.Toolbar xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/appbar" android:layout_height="?attr/actionBarSize" android:layout_width="match_parent" android:minHeight="?attr/actionBarSize" android:background="?attr/colorPrimary" android:elevation="4dp" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" app:popupTheme="@style/ThemeOverlay.AppCompat.Light" /> <!-- Resto de la interfaz de usuario --> <FrameLayout android:id="@+id/content_frame" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>
Para que la toolbar se visualice correctamente habrá que definir por supuesto el tema de la aplicación, los colores principales, … tal y como ya vimos en el artículo sobre el componente Toolbar.
Un último detalle de la configuración XML de la aplicación, y que nos servirá para terminar de conseguir el efecto deseado. Para versiones de Android 5.0 o superior (API >= 21) debemos añadir a la definición del tema algunos atributos adicionales, para lo que crearemos un fichero styles.xml específico de dicha versión (/res/values-21/styles.xml) con las siguientes definiciones:
<resources> <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <item name="colorPrimary">@color/color_primary</item> <item name="colorPrimaryDark">@color/color_primary_dark</item> <item name="colorAccent">@color/color_accent</item> <item name="android:windowDrawsSystemBarBackgrounds">true</item> <item name="android:statusBarColor">@android:color/transparent</item> </style> </resources>
Definida la interfaz XML, nos centramos ya en la parte java de la actividad. En primer lugar vamos a crear los fragments que mostraremos al seleccionar cada una de las tres opciones del menú de navegación. En este paso no nos vamos a complicar ya que no es el objetivo de este artículo. Voy a crear un fragment por cada opción, que contenga tan sólo una etiqueta de texto indicando la opción a la que pertenece. Obviamente en la práctica esto no será tan simple y habrá que definir cada fragment para que se ajuste a las necesidades de la aplicación.
Como ejemplo muestro el layout XML y la implementación java de uno de los layout. Primero el layout (fragment_fragment1.xml) :
<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" tools:context="net.sgoliver.android.navigationdrawer.Fragment1"> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:text="@string/fragment1" /> </FrameLayout>
Y su clase java asociada (Fragment1.java), que se limitará a inflar el layout anterior:
import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; public class Fragment1 extends Fragment { public Fragment1() { // Required empty public constructor } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment return inflater.inflate(R.layout.fragment_fragment1, container, false); } }
Los dos fragments restantes serán completamente análogos al mostrado.
Lo siguiente será implementar la lógica necesaria para responder a los eventos del menú de forma que cambiemos de fragment al pulsar cada opción. Esto lo haremos implementando el evento onNavigationItemSelected() del control NavigationView del menú lateral, lógica que añadiremos al final del método onCreate() de nuestra actividad principal.
drawerLayout = (DrawerLayout)findViewById(R.id.drawer_layout); navView = (NavigationView)findViewById(R.id.navview); navView.setNavigationItemSelectedListener( new NavigationView.OnNavigationItemSelectedListener() { @Override public boolean onNavigationItemSelected(MenuItem menuItem) { boolean fragmentTransaction = false; Fragment fragment = null; switch (menuItem.getItemId()) { case R.id.menu_seccion_1: fragment = new Fragment1(); fragmentTransaction = true; break; case R.id.menu_seccion_2: fragment = new Fragment2(); fragmentTransaction = true; break; case R.id.menu_seccion_3: fragment = new Fragment3(); fragmentTransaction = true; break; case R.id.menu_opcion_1: Log.i("NavigationView", "Pulsada opción 1"); break; case R.id.menu_opcion_2: Log.i("NavigationView", "Pulsada opción 2"); break; } if(fragmentTransaction) { getSupportFragmentManager().beginTransaction() .replace(R.id.content_frame, fragment) .commit(); menuItem.setChecked(true); getSupportActionBar().setTitle(menuItem.getTitle()); } drawerLayout.closeDrawers(); return true; } });
Comentemos un poco el código anterior.
Para las tres secciones principales lo que hacemos en primer lugar es crear el nuevo fragment a mostrar dependiendo de la opción pulsada en el menú de navegación, que nos llega como parámetro (menuItem) del evento onNavigationItemSelected. En el siguiente paso hacemos uso del Fragment Manager con getSupportFragmentManager() para sustituir el contenido del FrameLayout que definimos en el layout de la actividad principal por el nuevo fragment creado. Posteriormente marcamos como seleccionada la opción pulsada del menú mediante el método setChecked() y actualizamos el título de la action bar por el de la opción seleccionada mediante setTitle().
Por su parte, para las dos opciones finales del menú podemos realizar por ejemplo cualquier otra acción que no implique cambio de fragment (como abrir una actividad independiente para mostrar una ayuda o las opciones de la aplicación). En mi caso de ejemplo me limito a mostrar un mensaje de log llamando a Log.i().
Por último, y en cualquier caso, cerramos el menú llamando al método closeDrawers() del DrawerLayout.
Bien, pues ya tenemos la funcionalidad básica implementada, y sólo nos quedaría ajustar algunos detalles más para finalizar el trabajo. Los más importantes: deberíamos mostrar un indicador en la action bar que evidencie al usuario la existencia del menú lateral y deberíamos además permitir al usuario abrirlo haciendo click en dicho icono (además del gesto de deslizar desde el borde izquierdo hacia la derecha). Hasta la llegada de la nueva librería de diseño, esto se realizaba haciendo uso del componente ActionBarDrawerToggle, pero con la nueva librería el proceso se ha simplificado bastante y ya no es necesario la utilización de dicha clase.
Debemos primero incluir en nuestra aplicación el icono habitual que indica la existencia de un navigation drawer (hamburguer icon). Puedes crear tu propio icono desde cero, generarlo y descargarlo utilizando alguna herramienta online como Android Asset Studio, o simplemente descargar desde GitHub el que yo he utilizado como ejemplo para este artículo (se llama ic_nav_menu, y debes descargar el icono en todas sus resoluciones, desde las carpetas de drawables mdpi, hdpi, xhdpi, xxhdpi y xxxhdpi).
Una vez tenemos el icono incluido al proyecto, podemos utilizarlo como indicador del menú lateral llamando al método setHomeAsUpIndicator() desde el onCreate() de la actividad principal. Adicionalmente habilitaremos esta funcionalidad llamando también a setDisplayHomeAsUpEnabled().
@Override protected void onCreate(Bundle savedInstanceState) { //... appbar = (Toolbar)findViewById(R.id.appbar); setSupportActionBar(appbar); getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_nav_menu); getSupportActionBar().setDisplayHomeAsUpEnabled(true); //... }
Adicionalmente, tendremos que capturar en el método onOptionsItemSelected() de la actividad principal si se ha pulsado el icono. Para ello, evaluaremos si la opción de menú pulsada es android.R.id.home, en cuyo caso abriremos el menú lateral llamando al método openDrawer() del DrawerLayout:
@Override public boolean onOptionsItemSelected(MenuItem item) { switch(item.getItemId()) { case android.R.id.home: drawerLayout.openDrawer(GravityCompat.START); return true; //... } return super.onOptionsItemSelected(item); }
Llegados aquí, podemos ejecutar el proyecto y ver si todo funciona correctamente. Verificaremos que el menú se abra, que contiene las opciones indicadas y que al pulsar sobre ellas aparece en pantalla el contenido asociado. Verificaremos además que el título de la action bar se va actualizando según el estado del menú y la opción seleccionada.
Si lo ejecutamos sobre Android 5.x lo veremos como se muestra en la imagen anterior de este artículo (el menú se desliza por debajo de la status bar translúcida), y sobre Android 4.x se vería de forma casi idéntica a excepción de la barra de estado que sería negra:
Espero que estos últimos artículos os hayan servido para aprender a construir aplicaciones utilizando los componentes de diseño más representativos de la plataforma Android (la Action Bar y el Navigation Drawer).
Puedes consultar y/o descargar el código completo de los ejemplos desarrollados en este artículo accediendo a la pagina del curso en GitHub.
22 comentarios
Buenas,
tengo una pregunta con respecto este tutorial. He estado intentando añadir pestañas basándome en el anterior manual tuyo, pero no he sido capaz de hacerlo, se me para la app y ahí queda.
Desde mi proprio menú drawer (plantilla de otra web), he ido añadiendo paso a paso todos los elementos para las tabs, pero me da el siguiento error:
‘java.lang.RuntimeException: Unable to start activity ComponentInfo{com.android4dev.navigationview/com.android4dev.navigationview.MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method ‘void android.support.v4.view.ViewPager.setAdapter(android.support.v4.view.PagerAdapter)’ on a null object reference’
Tengo una duda, cuando entro digamos en la sección1 y le doy al boton atras no me lleva a la activity principal directamente cierra la aplicación, alguna forma para que vuelva atras.
Gacias.
buenas tardes oliver, estoy migrando una aplicacion que ya tenia para agregar un navigator drawer, mi duda es la siguiente, es posible cambiar las opciones que muestra el menu en el navigator drawer?
es decir si en el activity main muestra como opciones
ventas
Clientes
CxC
etc , al presionar la opcion Ventas y despliegue los fragments correspondientes mi navigator drawer muestre ahora las opciones
Nueva
Buscar
Agregar Producto
Regresar
o mi navigator siempre tendra las mismas opciones y tendria que usar otro tipo de menu por moulo?
de antemano muchas gracias y te felicito por tu aportacion de curso, es el mas acertado que he encontrado y es de gran ayuda
Buen dia.-
Amigo Oliver sigo tus indicaciones paso a paso pero se me presente un problema, al mostrme el activity el menu aunque se muestra esta en blanco totalmente.
no genera errores de compilacion ni de ejecucion simplemente, cuando se desliza el menu este esta completamente blanco.
Alguna razon o comentario te lo agradeceria!!!!!
Buen dia
Oliver serias tan amable de indicarme como hacer para que apenas abra mi aplicación me aparezca un fragment y no la app en blanco?. gracias
Es necesario ser un guru en Java para aprender Android, por que la verdad quisiera aprender mucho ello y no deprimirme y frustrarme por favor cual seria su consejos. gracias
Fantástico artículo!
Gracias!
Saludos el tutorial es la onda colega,
pero me ha surgido una duda realmente inquietante como pasaría un objeto Serializable a un Fragment
ya sea entre Actividad a Fragment o Fragment a Fragment creen que se pueda realizar? o es un sueño guajiro :P
Saludos
Hola buenas, enhorabuena por el tutorial.
Use este sistema de menu lateral en una de mis apps, y todo perfecto.
El problema viene ahora (haber si me puedes ayudar ^^):
Antes de acceder ala pantalla con el menu lateral, tengo un login.
Estoy intentado recuperar el nombre de usuario que use para logearme, y mostrarlo en el header_navview, donde tu pones Usuario. No lo e conseguido
Nose como llegar a ello, al no tener vinculada la clase xml con una activity me siento un poco tonto jejeje.
Espero q me contestes, un Saludo y gracias
Hola, espero puedan contestarme… Cómo tengo que hacer si a la seccion 1 quiero que salgan 2 pestañas Tablayout?
Hola buenas, gran tutorial! eso lo primero…
tengo un problema con el FragmentTransaction, me va todo perfecto, EXCEPTO
que no me borra el anterior fragment para dar lugar al nuevo, sino que van solapandose uno encima de otro
e copiado el codigo tal cual asiq no entiendo que puede ocurrir
gracias de antemano ;)
Hola! Gracias por el tutorial y tu aporte. Como comentario siento que te hizo falta bastante explicación. Claro, en temas que se te fueron un poquito, como por ejemplo de que no agregas los en ninguna parte, yo supo ponerlo pero quizá algunos otros no. Y el gran problema que he estado teniendo y que me afecto que no pusieras el código completo son, los import. Temas como el DrawaerLayout-NavigationView me dan muchos problemas, no me los reconoce el IDE y de ahí no lo saco, lo métodos relacionados con el ‘appbar’ tampoco los reconoce. Espero me ayudes un poco. Muchisimas gracias :D Dejo mi correo por cualquier duda: jordanramirez.lph@gmail.com
Tengo un problema y es que el actionbar aparece por delante del drawer, ya probé tu forma de hacerlo incluso utilizando alguna otra api y siempre me ocurre lo mismo, alguna solucion??
Buenas noches.
Realizando todo lo mostrado en este tutorial, resulta que funciona el Navigation Drawer pero tengo un pequeño problema. Al inicio de la aplicación tengo dos TexViews con sus correspondientes textos. Pues resulta que cuando accedo a una de las opciones del menú me aparece además de lo que tengo en el Fragment correspondiente, los dos textos del inicio. ¿Cómo puedo hacer para que al pulsar una opción en el menú simplemente se vea lo que haya en ese Fragment sin incluir el texto del inicio?
Muchas gracias,
Estimado Oliver,
De antemano te agradezco mucho por el tuto, me ha servido mucho, sin embargo quiero preguntarte algo, cuando genera el menu, veo un campo llamado «Usuario», como hago para cambiar dicho campo, ya que lo llenas automaticamente desde una variable en el Strings.xml pero quiero hacerlo dinamico.
Te agradezco mucho por la atención,
Saludos Cordiales
Jorge Medina
@Jorge Medina
Para modificar la variable usuario puedes hacer lo siguiente:
TextView tv = (TextView) navView.getHeaderView(0).findViewById(R.id.usuario);
tv.setText(«Modificando variable»);
a mi me funciono.
Saludos.
Hola y cómo se puede ampliar el drawer? Que sea más largo, por ejemplo, en la foto llega un poco antes del icono de la batería, pero supongamos…para que llegue a donde está la hora? Qué tengo que modificar? Gracias
@Carlos
Te agradezco mucho, he logrado realizar el cambio que queria. ahora me encuentro con otra interrogante mucho mayor a esta, no se si puedas ayudarme, puedes darme tu mail o tu skype ;D
Espero que puedas ayudarme
Saludos Cordiales
Jorge Medina
como aumentar el tamaño de los iconos de los items
Agregar en onCreate
if (savedInstanceState == null) {
Fragmento1 f1= new HomeFragment();
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.add(R.id.content_frame, f1);
fragmentTransaction.commit();
}
adicionar el método en la clase
@Override
public void onFragmentInteraction(Uri uri){
}
Adicionar implementaciones en la cabecera por cada fragmento usado
implements Fragmento1.OnFragmentInteractionListener, Fragmento2.OnFragmentInteractionListener
Agregar todo lo anterior para que funcione el app
Hola!! veo que en este ejemplo usas Fragments hay alguna diferencia entre usar los Fragments desde el navigation y usar Intent???
Esque entendia los Fragments estilo dialogos para pedir pocos datos. Y los intent como que tiene vida propia.
Si me puedes resolver te lo agradeceria.