Este artículo te explicará el uso del Navigation Drawer en Android para crear una navegación a través de un Menú deslizante.
Verás como implementar un Drawer Layout para el diseño. Aprenderás a manejar los eventos relacionados. Y también a complementar su funcionamiento con la action bar mediante un Action Bar Drawer Toggle.
Antes de iniciar quiero que veas la aplicación que podrás construir, una vez hayas aprendido todas las lecciones de este tutorial:
¿Genial cierto?, puedes descargar el código completo desde aquí:
[sociallocker id=»7121″]
¿Cómo crear un menú deslizante con un Navigation Drawer?
El Navigation Drawer es un panel deslizante cuyo objetivo es dotar al usuario con una navegación más cómoda entre las opciones populares de tu aplicación.
Existen dos formas para visualizar su contenido: La primera es deslizando desde el borde izquierdo de la pantalla hacia la derecha. Y la segunda al presionar el ícono de la Action Bar si es que se usó un elemento especial llamado ActionBarDrawerToggle.
A continuación se muestran algunos ejemplos de un Navigation Drawer:
Como ves, este componente es una de las tantas formas de navegación en una aplicación Android. La gran ventaja de utilizarlo es la expansión de funcionalidades que brinda sin tener que cambiar entre actividades.
Ejemplo de Navigation Drawer en Android
La aplicación que construiremos le llamaremos «GeekyWeb». Esta es un excelente ejemplo para implementar un Navigation Drawer que brinde al usuario accesibilidad hacia las categorías principales de los artículos en un blog sobre Desarrollo Web.
Categorías como: HTML, CSS, Javascript, Angular JS, Python y Ruby on Rails.
Cada vez que el usuario seleccione una categoría desde el Navigation Drawer, el layout principal se actualizará para proyectar su respectivo contenido.
Estructuralmente este proyecto se compone de un DrawerLayout que contiene un RelativeLayout para la proyección de contenido y un ListView (con su respectivo adaptador personalizado) para las opciones al desplegarse.
La idea es ir reemplazando el contenido del RelativeLayout con fragmentos personalizados cada vez que se presione una opción.
Crear un Proyecto en Android Studio
Abre Android Studio y crea un nuevo proyecto (GeekyWeb) con una actividad en blanco. Llámala Main y prosigue. En este punto cabe destacar que para crear un Navigation Drawer necesitamos usar la librería de compatibilidad v4 de Android.
Así que tenemos que importarla a nuestro proyecto. Para ello sigue estos pasos:
1. Asegúrate de haberla descargado la sección Support en el SDK Manager.
2. Abre tu archivo de construcción build.gradle.
3. Ubícate en dependencies y escribe la siguiente línea:
dependencies { compile 'com.android.support:support-v4:19.1.0' }
Esta instrucción le indica a gradle que necesitas incluir como dependencia externa la librería de soporte v4, con una compatibilidad objetivo del SDK 19. Según la versión que vayas a compilar pues así mismo puedes especificar el número y cambiarla.
Crear un DrawerLayout
Un DrawerLayout es un contenedor especial de la librería de soporte, que alberga dos tipos de contenido, el contenido principal y el contenido para el Navigation Drawer.
El contenido principal es el contenedor que veremos en la actividad normalmente. El contenido del drawer será aquel View que veremos cuando se despliegue el Navigation Drawer, donde frecuentemente se usa un ListView con varias opciones de selección.
Para implementarlo se debe definir un nodo raíz <android.support.v4.widget.DrawerLayout> en el archivo de diseño de la actividad. Luego añade dos hijos. El primero debe ser el layout que representará el contenido principal y el segundo el componente que representará el Drawer.
Fíjate en el archivo de diseño activity_main.xml de la aplicación GeekyWeb:
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent"> <RelativeLayout android:id="@+id/content_frame" android:layout_width="match_parent" android:layout_height="match_parent" /> <ListView android:id="@+id/left_drawer" android:layout_width="240dp" android:layout_height="match_parent" android:layout_gravity="start" android:choiceMode="singleChoice" android:divider="@android:color/transparent" android:dividerHeight="0dp" android:background="#111"/> </android.support.v4.widget.DrawerLayout>
El contenido principal es representado por un RelativeLayout y el Navigation Drawer es un ListView.
Crear Adaptador Personalizado para el ListView
El Navigation Drawer de GeekyWeb tiene una lista cuyos ítems poseen un icono por cada elemento y el texto que representa la opción. Si has visto el Tutorial de Listas y Adaptadores en Android ya sabrás como abordar este diseño.
Se implementa una clase que represente el ítem de la lista para relacionar un recurso drawable y un string.
Luego diseñamos un nuevo layout con la imagen y el nombre. Y finalmente extendemos la clase ArrayAdapter para relacionar los datos.
El archivo de diseño para los ítems de la lista quedaría así:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="match_parent" android:layout_width="match_parent" android:padding="10dp"> <TextView android:id="@+id/name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:textColor="#fff" android:text="Item" android:layout_toRightOf="@+id/icon" android:layout_centerVertical="true" /> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/icon" android:layout_alignParentLeft="true" android:layout_marginRight="16dp" android:layout_centerVertical="true" /> </RelativeLayout>
La clase representativa de cada elemento se denominó DrawerItem y esta es su definición:
public class DrawerItem { private String name; private int iconId; public DrawerItem(String name, int iconId) { this.name = name; this.iconId = iconId; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getIconId() { return iconId; } public void setIconId(int iconId) { this.iconId = iconId; } }
Ahora solo queda implementar el adaptador usando como tipo de entrada los elementos de la clase DrawerItem:
public class DrawerListAdapter extends ArrayAdapter { public DrawerListAdapter(Context context, List objects) { super(context, 0, objects); } @Override public View getView(int position, View convertView, ViewGroup parent) { if(convertView == null){ LayoutInflater inflater = (LayoutInflater)parent.getContext(). getSystemService(Context.LAYOUT_INFLATER_SERVICE); convertView = inflater.inflate(R.layout.drawer_list_item, null); } ImageView icon = (ImageView) convertView.findViewById(R.id.icon); TextView name = (TextView) convertView.findViewById(R.id.name); DrawerItem item = getItem(position); icon.setImageResource(item.getIconId()); name.setText(item.getName()); return convertView; } }
Poblar el ListView del Navigation Drawer
Una vez definido nuestro adaptador, obtendremos una instancia del DrawerLayout en el método onCreate() de Main. Luego de ello obtendremos el ListView. Y después crearemos una instancia del adaptador para añadirle una lista predeterminada de ítems. Veamos:
//Obtener arreglo de strings desde los recursos tagTitles = getResources().getStringArray(R.array.Tags); //Obtener drawer drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); //Obtener listview drawerList = (ListView) findViewById(R.id.left_drawer); //Nueva lista de drawer items ArrayList<DrawerItem> items = new ArrayList<DrawerItem>(); items.add(new DrawerItem(tagTitles[0],R.drawable.ic_html)); items.add(new DrawerItem(tagTitles[1],R.drawable.ic_css)); items.add(new DrawerItem(tagTitles[2],R.drawable.ic_javascript)); items.add(new DrawerItem(tagTitles[3],R.drawable.ic_angular)); items.add(new DrawerItem(tagTitles[4],R.drawable.ic_python)); items.add(new DrawerItem(tagTitles[5],R.drawable.ic_ruby)); // Relacionar el adaptador y la escucha de la lista del drawer drawerList.setAdapter(new DrawerListAdapter(this, items));
Como ves, el texto de cada ítem de la lista fue obtenido desde un array de strings llamado Tags, el cual está definido de la siguiente forma en strings.xml:
<string-array name="Tags"> <item>HTML</item> <item>CSS</item> <item>JavaScript</item> <item>Angular JS</item> <item>Python</item> <item>Ruby</item> </string-array>
Personalizar el Layout de los Fragmentos
Habíamos dicho que el contenido principal de la actividad iba a variar con respecto a la opción elegida en la lista. Para hacer efectivo este comportamiento, primero debemos crear el diseño de cada fragmento y extender una nueva clase de Fragment (Lee también Fragmentos en una Aplicación Android).
El fragmento simplemente mostrará un ImageView estático más un texto que indica el ítem seleccionado en el Navigation Drawer. Veamos:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="match_parent" android:layout_width="match_parent" android:padding="10dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:text="@string/headlines" android:id="@+id/headline" android:layout_centerHorizontal="true" /> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/imageView" android:layout_centerHorizontal="true" android:layout_below="@+id/headline" android:src="@drawable/article" /> </RelativeLayout>
Ahora implementaremos la clase ArticleFragment:
public class ArticleFragment extends Fragment { public static final String ARG_ARTICLES_NUMBER = "articles_number"; public ArticleFragment() { // Constructor vacío obligatorio } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_article, container, false); int i = getArguments().getInt(ARG_ARTICLES_NUMBER); String article = getResources().getStringArray(R.array.Tags)[i]; getActivity().setTitle(article); TextView headline = (TextView)rootView.findViewById(R.id.headline); headline.append(" "+article); return rootView; } }
Cuando un ArticleFragment es creado se cambiará inmediatamente el título de la actividad por una cadena del array de strings. Para saber que cadena es, se obtiene el argumento enviado desde la actividad principal (ARG_ARTICLES_NUMBER), que representa la posición del ítem en la lista.
Luego se concatena el texto por defecto que tiene el TextView del fragmento más el nombre de la categoría.
GeekyWeb tiene una sola clase para todos los fragmentos, ya que cumplen con el mismo patrón, pero tal vez en tu caso no sea de esta forma. Crea distintos fragmentos personalizados si cada opción muestra un contenido con estructura particular.
Manejar los eventos de la lista en el Navigation Drawer
Cuando un ítem de la lista es clickeado, inmediatamente el contenido principal debe actualizarse para la selección. Dicha actualización se lleva a cabo a través del método selectItem(), el cual se invoca dentro de onItemClick() (requiere el uso de la escucha OnItemClickListener de la lista).
Este método debe reemplazar el contenido que se encuentra en el RelativeLayout del DrawerLayout por un nuevo fragmento adaptado a las condiciones de selección.
Veamos:
private void selectItem(int position) { // Crear nuevo fragmento Fragment fragment = new ArticleFragment(); //Mandar como argumento la posición del item Bundle args = new Bundle(); args.putInt(ArticleFragment.ARG_ARTICLES_NUMBER, position); fragment.setArguments(args); //Reemplazar contenido FragmentManager fragmentManager = getFragmentManager(); fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit(); // Se actualiza el item seleccionado y el título, después de cerrar el drawer drawerList.setItemChecked(position, true); setTitle(tagTitles[position]); drawerLayout.closeDrawer(drawerList); }
El método selectItem() recibe como parámetro la posición del ítem seleccionado, el cual debes enviar como argumento con la constante definida en ArticleFragment. Luego de reemplazar el fragmento en el contenido principal, pasamos a iluminar el ítem de la lista (setItemChecked()), cambiar el título de la action bar (setTitle()) y luego cerrar el drawer(closeDrawer()).
Ahora solo declaras una clase interna extendida de OnItemClickListener y ejecutas selectItem() al interior de onItemClick():
/* La escucha del ListView en el Drawer */ private class DrawerItemClickListener implements ListView.OnItemClickListener { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { selectItem(position); } }
Luego setea una instancia de esta escucha en la lista del Navigation Drawer:
drawerList.setOnItemClickListener(new DrawerItemClickListener());
Manejar los eventos de apertura y cierre del Navigation Drawer
Usa la interfaz DrawerListener para manejar los eventos de apertura, cierre y sliding del Navigation Drawer. En primera instancia debes declarar una nueva escucha y setearla al DrawerLayout con el método setDrawerListener().
Luego implementa el método callback onDrawerOpened() que te permite actuar cuando el drawer se expande y onDrawerClosed() cuando se cierra.
Veamos un breve ejemplo:
drawerToggle = new ActionBarDrawerToggle( this, drawerLayout, R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close ) { public void onDrawerClosed(View view) { //Acciones que se ejecutan cuando se cierra el drawer } public void onDrawerOpened(View drawerView) { //Acciones que se ejecutan cuando se despliega el drawer } }; //Seteamos la escucha drawerLayout.setDrawerListener(drawerToggle);
No te preocupes por el objeto de drawerToggle, en la siguiente sección veremos su uso. Quiero que te concentres en la implementación de los métodos de la escucha. Simplemente se declara una clase anónima con la sobrescritura de los métodos de abrir y cerrar. Ambos reciben como parámetro una instancia del drawer para que manipules cualquiera de sus aspectos.
Usar un ActionBarDrawerToggle con el icono de la app
Un ActionBarDrawerToggle es un elemento que se implementa sobre la Action Bar para abrir y cerrar un Navigation Drawer con el icono de la aplicación. Normalmente se representa con un drawable de tres barras horizontales, que se contrae y expande al abrir y cerrar el drawer. La siguiente ilustración muestra su aspecto:
Este componente puede accionar el Navigation Drawer debido a que implementa la escucha DrawerListener en su definición. Por esta razón podemos crear una nueva instancia del ActionBarDrawerToggle y setearla como escucha de nuestro DrawerLayoutcomo hicimos en la sección anterior. Ten en cuenta su constructor:
drawerToggle = new ActionBarDrawerToggle( this, drawerLayout, R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close ) { ...
El primer parámetro es el contexto donde se ejecuta. También necesita la instancia del DrawerLayout con que se relacionará. El tercer parámetro es el drawable que representará su presencia, en este caso es el recurso del sistema R.drawable.ic_drawer.
Y por ultimo dos strings de accesibilidad que contienen la información sobre la apertura y cierre del Drawer.
Sabiendo esta información podemos implementar nuestros eventos de apertura y cierre del Navigation Drawer. La idea es cambiar al título de la aplicación cuando el drawer se abra. Pero cuando esté cerrado se cambiará por el título del elemento seleccionado en la lista.
Veamos:
drawerToggle = new ActionBarDrawerToggle( this, drawerLayout, R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close ) { public void onDrawerClosed(View view) { //Cambiar título por nombre del item en la lista getActionBar().setTitle(itemTitle); } public void onDrawerOpened(View drawerView) { //Cambiar título por nombre de la actividad getActionBar().setTitle(activityTitle); } }; //Seteamos la escucha drawerLayout.setDrawerListener(drawerToggle);
Se usan dos variables auxiliares para guardar el título de la actividad (activityTitle) y el de la categoría actual (itemTitle).
Finalmente solo queda implementar los métodos callback onPostCreate() y onConfigurationChanged() para que nuestro toggle se actualice con los cambios de la aplicación.
@Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); // Sincronizar el estado del drawer drawerToggle.syncState(); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); // Cambiar las configuraciones del drawer si hubo modificaciones drawerToggle.onConfigurationChanged(newConfig); }
Dentro de onPostCreate() usaremos el método syncState() para sincronizar el estado del toggle si en algún momento la aplicación es puesta en segundo plano. Y en onConfigurationChanged() propagaremos los cambios del dispositivo.
Cambios como la modificación de entradas por teclado, tamaño de la pantalla o el cambio de orientación de pantalla.
Adicionalmente puedes tomar acción cuando el toggle sea presionado en la action bar con el método onOptionsItemSelected(), igual a como se hace con los action buttons:
@Override public boolean onOptionsItemSelected(MenuItem item) { if (drawerToggle.onOptionsItemSelected(item)) { // Toma los eventos de selección del toggle aquí return true; } ...//Manejo de los action buttons return super.onOptionsItemSelected(item); }
Para terminar ejecuta el proyecto y tendrás tu aplicación GeekyWeb con el siguiente aspecto:
Iconos cortesía de Icon Finder