Aplicación Android Con Navigation Drawer Y Tabs

En este artículo aprenderás a crear una aplicación Android que contenga Navigation Drawer y Tabs de forma conjunta. La idea es crear secciones con diferentes fragmentos y ver como introducir una gran variedad de layouts.

El ejemplo que verás se trata de una aplicación que ofrece a los usuarios diferentes platos de comida pertenecientes a un restaurante hipotético llamado “Rico Pa’ Rico”.

Crearemos cuatro secciones del navigation drawer para comprobar cómo se sortean situaciones similares a estas:

  • Un navigation drawer con fragmentos diferentes.
  • Un navigation drawer con tabs.
  • Listas y grid views en tabs.
  • Tabs con fragmentos diferentes.
  • Iniciar una actividad de configuración desde un navigation drawer.

Si sigues leyendo podrás obtener un resultado similar a este:

Para ver el link de descarga del proyecto completo en Android Studio, sigue estas instrucciones:

#1. Planeación De Pantallas Y Sus Relaciones

Antes de comenzar con el desarrollo, conozcamos un poco más de la app.

Con la aplicación móvil Rico Pa’ Rico se pretende mostrar:

  • Un inicio con los productos más populares entre los clientes del restaurante.
  • Los datos de la cuenta del usuario como su perfil, las direcciones que tiene asociadas para la entrega de productos a domicilio y la información de las tarjetas que ha usado para pagos.
  • También se necesita mostrar el menú de comidas divididos en tres categorías: Platillos, Bebidas y Postres. Sería ideal mostrar el precio y el nombre más una pequeña imagen de la comida.
  • Posibilitar la configuración del envío de correos informativos a la cuenta del usuario. Las frecuencias de envío son: diario, semanal, mensual o sin envío.

Básicamente esos son los requerimientos de la app. Como ves, hacen falta varias funcionalidades para tener un servicio a domicilio completo, sin embargo, estas características permitirán entender a la perfección el uso de un navigation drawer y tabs.

A partir de los requerimientos se deduce que se necesita la siguiente lista de pantallas:

  • Menú principal para las secciones disponibles
  • Inicio con una lista de los elementos populares
  • Sección para los datos de la cuenta
  • Sección para las categorías
  • Pantalla de configuración de notificaciones
  • Detalle del perfil del usuario
  • Lista de direcciones
  • Lista de tarjetas o pantalla con aviso en blanco
  • Grilla de platillos
  • Grilla para bebidas
  • Grilla para postres

Ahora representemos las relaciones entre pantallas a través de un mapa relacional. La idea es mostrar los posibles accesos desde una pantalla hacia otra.

Screen Map De Aplicación Android

El siguiente paso es decidir los patrones convenientes para representar la estructura del mapa anterior. Es aquí donde encaja el patrón del Navigation Drawer.

¿Pero por qué usar un navigation drawer en esta aplicación?

Porque existen algunas secciones que tienen dos niveles de navegación o incluso la forma en que estaría organizado el modelo de datos de esta aplicación muestra subdivisión en los datos.

En ese caso la documentación de Material Design sugiere añadir este elemento como primer nivel y dejar en segundo nivel las subcagtegorías a través de tabs.

Navegación En Material Design Con Navigation Drawer

En primer nivel estarían cuatro categorías principales asociadas al funcionamiento de la aplicación como lo son Inicio, Mi Cuenta, Categorías (o Menú) y Configuración.

Dentro de la cuenta podemos crear varias subdivisiones entre el perfil, las direcciones y las tarjetas. Este sería un segundo nivel.

Al igual que en categorías, donde encontramos valga la redundancia, tres subcategorías para segmentar.

Teniendo esto claro, dentro del mapa de pantallas podemos reemplazar los elementos Menú Principal, Mi Cuenta y Categorías por los patrones necesarios.

Patrones En Screen Map De Aplicación Android

#2. Wireframe Aplicación Android

El segundo paso es bocetar las ideas generales de funcionalidad que tenemos. Para ello usaré una aplicación online gratuita para crear wireframes de apps móviles llamada Ninja Mock.

Su funcionamiento es sencillo. Simplemente te registras y luego abres tu escritorio de apps. Una vez allí presionas New Project para crear un nuevo proyecto.

New Project Ninja Mock

En seguida seleccionas la categoría ANDROID.

Android WireFrame NinjaMock

Y con ello se abrirá tu espacio de trabajo para que realices los diseños necesarios. Tendrás a disposición gran variedad de elementos visuales en el panel izquierdo, así como la posibilidad de editar sus propiedades en el panel derecho.

Workspace En Ninja Mock

Sin embargo este apartado no es un tutorial sobre ninja mock. Espero esta introducción te facilite probar sus funcionalidades.

 

Nota: Si quieres aprender más sobre wireframing y diseño de apps móviles, entonces te recomiendo tomar el curso Curso de diseño de aplicaciones para iOs y Android.

Basado en el mapa de pantallas con patrones definidos de la anterior sección, el boceto de nuestra app quedaría de la siguiente forma:

Wireframe Aplicacion Android Con Navigation Drawer

Ahora lo que sigue es crear cada uno de los componentes gráficos necesarios para generar la aplicación.

#3. Navigation Drawer Con Diferentes Fragmentos

Cada una de las pantallas de nuestra aplicación será contenida en una serie de fragmentos relacionados dentro del navigation drawer.

Solo tendremos dos actividades para manejar la estructura completa. Una actividad principal y la de ajustes o configuración.

Comencemos…

1. Abre Android Studio y crea un nuevo proyecto llamado “Restaurante Rico Pa Rico”. Añade por defecto una actividad en blanco con el nombre ActividadPrincipal.java y confirma.

2. Prepara las características mínimas del proyecto para su funcionamiento. Define la paleta de colores para material design dentro de tu archivo colors.xml de la siguiente forma:

colors.xml

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

    <color name="primaryDarkColor">#323232</color>
    <color name="primaryColor">#484848</color>
    <color name="accentColor">#F87652</color>

    <color name="background_footer_item_grid_categorias">#90000000</color>

    <color name="color_light">#ffffff</color>

    <color name="background_header_seccion_inicio">#4B4B4B</color>
</resources>

No te preocupes por los demás valores, ya que los usaremos más adelante.

Ahora añade los siguientes strings para la interfaz.

strings.xml

<resources>
    <string name="app_name">"Restaurante Rico Pa Rico "</string>

    <string name="action_settings">Configuración</string>
    <string name="action_search">Buscar</string>
    <string name="action_carrito">Añadir al carrito</string>

    <string name="item_inicio">Inicio</string>
    <string name="item_cuenta">Mi Cuenta</string>
    <string name="item_categorias">Categorías</string>
    <string name="item_configuracion">Configuración</string>

    <string name="titulo_comidas_populares">Lo más pedido</string>

    <string name="titulo_tab_platillos">PLATILLOS</string>
    <string name="titulo_tab_bebidas">BEBIDAS</string>
    <string name="titulo_tab_postres">POSTRES</string>

    <string name="titulo_tab_perfil">PERFIL</string>
    <string name="titulo_tab_direcciones">DIRECCIONES</string>
    <string name="titulo_tab_tarjetas">TARJETAS</string>

    <string name="texto_no_tarjetas">No tienes ninguna tarjeta asociada</string>

    <string name="etiqueta_carrito">Carrito</string>

    <string name="texto_cambiar_contrasena">Cambia tu contraseña</string>
    <string name="etiqueta_contrasena">Contraseña</string>
    <string name="etiqueta_info_usuario">Información del usuario</string>

</resources>

Importante declarar los estilos que usaremos. En nuestro caso tendremos dos estilos. Uno para la actividad principal, la cual usará el navigation drawer y requiere transparencia. Y otro para la actividad configuración, la cual usa el estilo por defecto.

styles.xml

<resources>

    <style name="Theme.RicoPaRico" 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>

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

Para versiones menores a 21, los atributos de transparencia no son soportados, por lo que dejamos el tema Theme.ConNavigationDrawer vacío. Sin embargo, en versiones mayores o iguales a 21 si requeriremos completar este estilo.

values-21/styles.xml

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

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

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

Por último añade las dependencias de las librerías que usaremos en el proyecto. Debido al tipo de estilo visual que usaremos, requeriremos la nueva librería de diseño, el uso de glide para carga de imágenes, la extensión de recycler view y cardview.

build.gradle

dependencies {
    ...
    compile 'com.android.support:appcompat-v7:23.0.1'
    compile 'com.android.support:design:23.0.1'
    compile 'com.android.support:recyclerview-v7:23.0.1'
    compile 'com.github.bumptech.glide:glide:3.6.1'
    compile 'com.android.support:support-v4:23.0.1'
    compile 'com.android.support:cardview-v7:23.0.1'
}

¡Ah! y muy importante, descarga los drawables del proyecto para completar la interfaz. A continuación el link de descarga.

3. Abre el layout de la actividad principal (en mi caso se llama actividad_principal.xml) y elimina su diseño por defecto para comenzar a construir uno basado en un navigation drawer.

Para tener una guía observaremos el diseño del wireframe bocetado anteriormente:

Wireframe De App Android Con Navigation Drawer

En este caso, es un navigation drawer con cabecera. Solo tiene 4 ítems con las secciones estudiadas.

La cabecera contiene el logo de la empresa en la parte superior alineada a la izquierda y por debajo una doble línea que representa la cantidad existente dentro del carrito de compras (incluso tiene un icono en la parte izquierda para aclarar la idea).

La creación es sencilla como vimos en el artículo NavigationView: Navigation Drawer Con Material Design, donde el nodo principal es un elemento DrawerLayout. Cuyo contenido han de ser dos elementos.

El contenido principal, representado por cualquier elemento visual y un componente NavigationView (librería de soporte de diseño) para la parte deslizante con el menú.

actividad_principal.xml

<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/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

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

    <!-- 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/cabecera_drawer"
        app:menu="@menu/menu_drawer" />

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

4. El header contenido principal lo declararemos en un layout externo llamado contenido_principal.xml. Dentro de él pondremos una estructura simple que contenga la App Bar con una Toolbar implementada y como contenedor principal irá un RelativeLayout, el cual será reemplazado por cada fragmento de las secciones.

contenido_principal.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.design.widget.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

        <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" />
    </android.support.design.widget.AppBarLayout>

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

</LinearLayout>

5. El header del navigation drawer lo crearemos en otro layout independiente llamado cabecera_drawer.xml. Básicamente necesitamos un linear layout que contenga una fila para el logo (ImageView) y otra para un TableLayout de 2×2 para ubicar el icono del carrito más la etiqueta y texto asociadas a las compras.

cabecera_drawer.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="wrap_content"
    android:background="@color/primaryColor"
    android:gravity="bottom"
    android:orientation="vertical"
    android:padding="@dimen/padding_izquierdo_cabecera"
    android:theme="@style/ThemeOverlay.AppCompat.Dark">

    <!-- Logo Rico Pa' Rico -->
    <ImageView
        android:id="@+id/imageView3"
        android:layout_width="@dimen/ancho_logo"
        android:layout_height="wrap_content"
        android:src="@drawable/logo" />

    <TableLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:columnCount="2"
        android:orientation="vertical"
        android:rowCount="2">


        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <ImageView
                android:id="@+id/icono_carrito"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginRight="8dp"
                android:src="@drawable/carrito_compras" />

            <TextView
                android:id="@+id/etiqueta_carrito"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_vertical"
                android:text="@string/etiqueta_carrito"
                android:textAppearance="@style/TextAppearance.AppCompat.Body1"
                android:textStyle="bold" />

        </TableRow>

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:id="@+id/texto_total_carrito"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_column="1"
                android:layout_gravity="center_vertical"
                android:text="$ 0"
                android:textAppearance="@style/TextAppearance.AppCompat.Body1"
                android:textStyle="bold" />
        </TableRow>

    </TableLayout>
</LinearLayout>

6. Lo siguiente es construir un recurso del tipo menu para implementar las secciones del navigation drawer. Para ello crea un nuevo archivo llamado menu_drawer.xml.

Recuerda que tendremos las siguientes secciones o ítems: Inicio, Mi Cuenta, Categorías y Configuración.

menu_drawer.xml

<?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/item_inicio"
            android:checked="true"
            android:icon="@drawable/inicio"
            android:title="@string/item_inicio" />
        <item
            android:id="@+id/item_cuenta"
            android:icon="@drawable/cuenta"
            android:title="@string/item_cuenta" />
        <item
            android:id="@+id/item_categorias"
            android:icon="@drawable/categorias"
            android:title="@string/item_categorias" />
        <item
            android:id="@+id/item_configuracion"
            android:icon="@drawable/configuracion"
            android:title="@string/item_configuracion" />
    </group>

</menu>

7. Por último modifica la lógica de la actividad principal.

Cambia el home button por el icono del navigation drawer y luego controla el evento de apertura dentro de onOptionsItemSelected(). Recuerda que esto lo hacemos el método DrawerLayout.openDrawer().

ActividadPrincipal.java

package com.herprogramacion.restaurantericoparico.ui;

import android.os.Bundle;
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 com.herprogramacion.restaurantericoparico.R;

public class ActividadPrincipal extends AppCompatActivity {

    private DrawerLayout drawerLayout;

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

        agregarToolbar();

        drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
    }

    private void agregarToolbar() {
        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.drawer_toggle);
            ab.setDisplayHomeAsUpEnabled(true);
        }

    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_actividad_principal, 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);
    }
}

Al ejecutar la aplicación tendrás algo como esto:

Ejemplo De Navigation Drawer En Android Con Material Design

#4. Sección De Navigation Drawer Con Fragmento De Lista

Siguiendo el orden, ahora es turno de crear la sección Inicio, al cual contiene una lista de las comidas más populares que tiene el restaurante. El boceto se ve así:

Wireframe De Fragmento Con RecyclerView

Abordar este diseño es fácil para nosotros, ya que sabemos muy bien cómo usar el RecyclerView para crear listas pobladas por adaptadores.

Básicamente necesitamos:

  • La clase Fragment para el contenido.
  • Un layout para el fragmento.
  • Un modelo de datos.
  • Un layout para los ítems.
  • Un adaptador para el recycler view.

Así que manos a la obra.

1. Añade un nuevo fragmento en blanco al proyecto llamado FragmentoInicio.java. Recuerda que puedes hacerlo a través del asistente File > New > Fragment > Fragment (Blank).

New Blank Fragment En Android Studio

2. Abre el layout asignado el fragmento. En mi caso se llama fragmento_inicio.xml. La idea es añadir un encabezado inicial con el texto "Lo más pedido" y por debajo un recycler view que muestre la lista de elementos.

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

    <TextView
        android:id="@+id/textView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:background="@color/background_header_seccion_inicio"
        android:gravity="center"
        android:padding="8dp"
        android:text="@string/titulo_comidas_populares"
        android:textAppearance="@style/TextAppearance.AppCompat.Body1"
        android:textColor="@color/color_light" />

    <android.support.v7.widget.RecyclerView
        android:id="@+id/reciclador"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical" />
</LinearLayout>

3. Crea una nueva clase Java llamada Comida. Su objetivo es representar cada comida que mostraremos a través de la aplicación, es decir, nuestra fuente de datos.

Según las especificaciones del boceto, simplemente necesitamos información sobre el precio, el nombre y el recurso de la imagen que representará su exquisitez.

Comida.java

package com.herprogramacion.restaurantericoparico.modelo;

import com.herprogramacion.restaurantericoparico.R;

import java.util.ArrayList;
import java.util.List;

/**
 * Modelo de datos estático para alimentar la aplicación
 */
public class Comida {
    private float precio;
    private String nombre;
    private int idDrawable;

    public Comida(float precio, String nombre, int idDrawable) {
        this.precio = precio;
        this.nombre = nombre;
        this.idDrawable = idDrawable;
    }

    public static final List<Comida> COMIDAS_POPULARES = new ArrayList<Comida>();
    public static final List<Comida> BEBIDAS = new ArrayList<>();
    public static final List<Comida> POSTRES = new ArrayList<>();
    public static final List<Comida> PLATILLOS = new ArrayList<>();

    static {
        COMIDAS_POPULARES.add(new Comida(5, "Camarones Tismados", R.drawable.camarones));
        COMIDAS_POPULARES.add(new Comida(3.2f, "Rosca Herbárea", R.drawable.rosca));
        COMIDAS_POPULARES.add(new Comida(12f, "Sushi Extremo", R.drawable.sushi));
        COMIDAS_POPULARES.add(new Comida(9, "Sandwich Deli", R.drawable.sandwich));
        COMIDAS_POPULARES.add(new Comida(34f, "Lomo De Cerdo Austral", R.drawable.lomo_cerdo));
    }

    public float getPrecio() {
        return precio;
    }

    public String getNombre() {
        return nombre;
    }

    public int getIdDrawable() {
        return idDrawable;
    }
}

El miembro COMIDAS_POPULARES será el flujo de datos que tendremos para poblar el adaptador.

4. Con los datos ya preparados ahora podemos crear el layout de los ítems, así que añade un nuevo archivo llamado item_lista_inicio.xml.

Usaremos un FrameLayout para sobreponer el precio y el nombre sobre la imagen de cada elemento.

item_lista_inicio.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/miniatura_comida"
        android:layout_width="match_parent"
        android:layout_height="@dimen/altura_miniatura_comida"
        android:scaleType="centerCrop" />

    <TextView
        android:id="@+id/precio_comida"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="left|bottom"
        android:layout_marginBottom="32dp"
        android:paddingLeft="@dimen/espacio_norma_1"
        android:text="Large Text"
        android:textColor="@android:color/white"
        android:textSize="48sp"
        android:textStyle="italic" />

    <TextView
        android:id="@+id/nombre_comida"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="left|bottom"
        android:paddingBottom="16dp"
        android:paddingLeft="16dp"
        android:text="Medium Text"
        android:textAppearance="@style/TextAppearance.AppCompat.Title"
        android:textColor="@android:color/white" />
</FrameLayout>

5. Crea una nueva clase llamada AdaptadorInicio.java para implementar el adaptador del recycler view para el inicio. Esta parte es muy fácil, solo no olvides alimentarlo con la lista COMIDAS_POPULARES.

AdaptadorInicio.Java

package com.herprogramacion.restaurantericoparico.ui;

import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import com.bumptech.glide.Glide;
import com.herprogramacion.restaurantericoparico.R;
import com.herprogramacion.restaurantericoparico.modelo.Comida;

/**
 * Adaptador para mostrar las comidas más pedidas en la sección "Inicio"
 */
public class AdaptadorInicio
        extends RecyclerView.Adapter<AdaptadorInicio.ViewHolder> {


    public static class ViewHolder extends RecyclerView.ViewHolder {
        // Campos respectivos de un item
        public TextView nombre;
        public TextView precio;
        public ImageView imagen;

        public ViewHolder(View v) {
            super(v);
            nombre = (TextView) v.findViewById(R.id.nombre_comida);
            precio = (TextView) v.findViewById(R.id.precio_comida);
            imagen = (ImageView) v.findViewById(R.id.miniatura_comida);
        }
    }

    public AdaptadorInicio() {
    }

    @Override
    public int getItemCount() {
        return Comida.COMIDAS_POPULARES.size();
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
        View v = LayoutInflater.from(viewGroup.getContext())
                .inflate(R.layout.item_lista_inicio, viewGroup, false);
        return new ViewHolder(v);
    }

    @Override
    public void onBindViewHolder(ViewHolder viewHolder, int i) {
        Comida item = Comida.COMIDAS_POPULARES.get(i);

        Glide.with(viewHolder.itemView.getContext())
                .load(item.getIdDrawable())
                .centerCrop()
                .into(viewHolder.imagen);
        viewHolder.nombre.setText(item.getNombre());
        viewHolder.precio.setText("$" + item.getPrecio());

    }


}

6. Con todos los componentes creados ya es posible poblar el recycler view desde el fragmento.

FragmentoInicio.java

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.herprogramacion.restaurantericoparico.R;

/**
 * Fragmento para la sección de "Inicio"
 */
public class FragmentoInicio extends Fragment {
    private RecyclerView reciclador;
    private LinearLayoutManager layoutManager;
    private AdaptadorInicio adaptador;

    public FragmentoInicio() {
    }

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

        reciclador = (RecyclerView) view.findViewById(R.id.reciclador);
        layoutManager = new LinearLayoutManager(getActivity());
        reciclador.setLayoutManager(layoutManager);

        adaptador = new AdaptadorInicio();
        reciclador.setAdapter(adaptador);
        return view;
    }

}

7. Finalmente prepararemos el navigation drawer para seleccionar elementos y responder a los eventos creando el nuevo fragmento de inicio.

En primera instancia obtén la instancia del NavigationView en el método onCreate() de la actividad principal.

NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);

Ahora comprueba en ese mismo lugar, si este está vacio. Si no es así, entonces prepara el navigation view y luego selecciona su ítem por defecto.

if (navigationView != null) {
    prepararDrawer(navigationView);
    // Seleccionar item por defecto
    seleccionarItem(navigationView.getMenu().getItem(0));
}

Para mí preparar el drawer significa setear la escucha de selección y configurar todos los atributos antes de iniciar con la selección. Para ello utilizo el método prepararDrawer().

private void prepararDrawer(NavigationView navigationView) {
    navigationView.setNavigationItemSelectedListener(
            new NavigationView.OnNavigationItemSelectedListener() {
                @Override
                public boolean onNavigationItemSelected(MenuItem menuItem) {
                    menuItem.setChecked(true);
                    seleccionarItem(menuItem);
                    drawerLayout.closeDrawers();
                    return true;
                }
            });

}

Recuerda onNavigationItemSelected nos provee un controlador para la selección. Dentro de este recibimos un objeto MenuItem para extraer toda la información del item seleccionado. Con setChecked() sombreamos el elemento seleccionado si usas true como parámetro.

La selección implica crear el contenido necesario de la sección, por lo que tenemos el método seleccionarItem() para ello.

Y como acción lógica una vez seleccionado cierra el drawer con closeDrawers() para seguir una secuencia limpia.

El método seleccionarItem() tiene un switch para determinar qué tipo de fragmento se creará una vez comparado el valor. Algo como esto:

private void seleccionarItem(MenuItem itemDrawer) {
    Fragment fragmentoGenerico = null;
    FragmentManager fragmentManager = getSupportFragmentManager();

    switch (itemDrawer.getItemId()) {
        case R.id.item_inicio:
            fragmentoGenerico = new FragmentoInicio();
            break;
        case R.id.item_cuenta:
            // Fragmento para la sección Cuenta
            break;
        case R.id.item_categorias:
            // Fragmento para la sección Categorías
            break;
        case R.id.item_configuracion:
            // Iniciar actividad de configuración
            break;
    }
    if (fragmentoGenerico != null) {
        fragmentManager
                .beginTransaction()
                .replace(R.id.contenedor_principal, fragmentoGenerico)
                .commit();
    }

    // Setear título actual
    setTitle(itemDrawer.getTitle());
}

Todo se define con el identificador del ítem que se obtiene a través de getItemId(). Este podemos compararlo con las constantes que tenemos en el archivo R.java.

Al final se reemplaza el contenido del RelativeLayout identificado por R.id.contenedor_principal que tenemos en actividad_principal.xml y luego se cambia el título de la actividad con el método getTitle().

Si corres el proyecto deberías ver la siguiente imagen:

Aplicación Android Con RecyclerView Restaurante

#5. Tabs Con Diferentes Fragmentos

Dentro de la sección Mi Cuenta es necesario usar tabs con fragmentos que tienen diferentes diseños. Por ejemplo, el diseño del perfil implementa dos cards para mostrar los datos de la cuenta.

Wireframe De Aplicación Android Para Un Perfil De Usuario

Sin embargo en las direcciones y tarjetas veremos distintas interfaces.

¿Cómo implementar este diseño en un TabLayout sabiendo que tiene distintos fragmentos?

De cierta manera, este asunto no tiene por qué afectar nuestro desarrollo, ya que el adaptador de fragmentos que usa a la hora de poblar un ViewPager puede recibir una lista de elementos genéricos de la clase Fragment. Lo que quiere decir que su método de inserción aceptará cualquier objeto que herede de esta clase.

El problema de noción está a la hora de incluir un ViewPager en el layout del fragmento. ¿Crees que es posible?

¡Claro que si!

En la documentación de Android se menciona que desde Android 4.2 es posible usar fragmentos anidados en tus aplicaciones, es decir, fragmentos dentro de otros fragmentos.

Fragmentos Anidados En Android 4.2

Por esta razón podemos incrustar el patrón Swipe Views dentro del layout de un fragmento con un soporte desde versiones 1.6.

Así que la idea de incorporar pestañas basadas en un fragmento es viable. En el caso de la sección mi cuenta necesitaremos:

  • Un Fragmento para las páginas de las pestañas y su layout correspondiente
  • Un fragmento para la sección PERFIL
  • Un fragmento para la sección DIRECCIONES
  • Un fragmento para la sección TARJETAS

También es posible crear un navigation drawer con diferentes actividades por sección para mantener el control de los layouts. Sin embargo ese será tema de un artículo futuro.

Veamos…

1. Añade un nuevo fragmento al proyecto y nómbralo FragmentoCuenta.java.

2. Abre su archivo de diseño y borrar el contenido por defecto. La idea es hacer de este fragmento el contenedor de las páginas que se coordinarán con las pestañas de la sección Mi Cuenta.

Como vimos en el artículo TabLayout: ¿Cómo Añadir Pestañas En Android?, si deseamos tener un efecto deslizante entre páginas de contenido debes añadir un ViewPager que ocupe el espacio total del fragmento.

fragmento_paginado.xml

<android.support.v4.view.ViewPager
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

3. El enfoque que estamos usando de solamente fragmentos dificulta insertar pestañas de forma descriptiva a través del layout del fragmento actual, ya que la App Bar se encuentra en el layout de la actividad principal. El actual fragmento lo que hace es reemplazar un contenedor de segundo nivel.

Así que para añadir las pestañas debemos hacer uso del diseño programático. Esto quiere decir que crearemos una nueva instancia de un objeto TabLayout y luego lo incrustaremos en la App Bar que está un nivel más arriba de la jerarquía de views.

La ventaja es que el método onCreateView() del fragmento trae en sus parámetros el contenedor padre de la jerarquía de views. Con este podemos buscar la instancia fácilmente y luego añadir las pestañas a través del método addView().

import android.graphics.Color;
import android.os.Bundle;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.herprogramacion.restaurantericoparico.R;

import java.util.ArrayList;
import java.util.List;


/**
 * Fragmento de la sección "Mi Cuenta"
 */
public class FragmentoCuenta extends Fragment {

    private AppBarLayout appBar;
    private TabLayout pestanas;

    public FragmentoCuenta() {
    }

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

        if (savedInstanceState == null) {
            insertarTabs(container);
        }

        return view;
    }

    private void insertarTabs(ViewGroup container) {
        View padre = (View) container.getParent();
        appBar = (AppBarLayout) padre.findViewById(R.id.appbar);
        pestanas = new TabLayout(getActivity());
        pestanas.setTabTextColors(Color.parseColor("#FFFFFF"), Color.parseColor("#FFFFFF"));
        appBar.addView(pestanas);
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        appBar.removeView(pestanas);
    }
}

Como ves, el método insertarTabs() es el encargado de añadir un nuevo TabLayout en la App Bar. Este recibe como parámetro el contenedor del fragmento que en este caso es el layout de la actividad principal.

Luego se obtiene una instancia de la app bar, consiguiendo el padre del contenedor a través de getParent() (en  contenido_principal.xml sería el elemento identificado con R.id.contenedor_principal) que sería un LinearLayout.

Esto permitirá añadir el view sin ningún problema. Obviamente al añadirlo, también hay que quitarlo al momento en que el fragmento se destruya con removeView().

Al ejecutar tendrás algo como:

Ejemplo TabLayout En Aplicación Android Librería De Diseño

4. A continuación crearemos el fragmento para la página PERFIL. Simplemente añade una nueva clase llamada FragmentoPerfil.

FragmentoPerfil.java

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

import com.herprogramacion.restaurantericoparico.R;

/**
 * Fragmento para la pestaña "PERFIL" De la sección "Mi Cuenta"
 */
public class FragmentoPerfil extends Fragment {

    public FragmentoPerfil() {
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragmento_perfil, container, false);
    }
}

Y luego diseña las tarjetas para los datos del usuario y la contraseña como se vió en el boceto al inicio.

fragmento_perfil.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#dee4ee"
    android:orientation="vertical"
    android:padding="@dimen/espacio_norma_1">

    <TextView
        android:id="@+id/titulo_informacion_usuario"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingBottom="8dp"
        android:text="@string/etiqueta_info_usuario"
        android:textAppearance="?android:attr/textAppearanceSmall" />

    <android.support.v7.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        card_view:cardCornerRadius="2dp"
        card_view:cardUseCompatPadding="true">

        <GridLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:columnCount="2"
            android:padding="@dimen/espacio_norma_1"
            android:rowCount="2">

            <ImageView
                android:id="@+id/imageView"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:layout_marginBottom="32dp"
                android:layout_marginRight="@dimen/espacio_norma_2"
                android:src="@drawable/usuario" />

            <TextView
                android:id="@+id/texto_nombre"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_vertical"
                android:layout_marginBottom="32dp"
                android:text="James Revelo"
                android:textAppearance="?android:attr/textAppearanceSmall" />

            <ImageView
                android:id="@+id/imageView2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:layout_marginRight="8dp"
                android:src="@drawable/email" />

            <TextView
                android:id="@+id/texto_email"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_vertical"
                android:text="james@mail.com"
                android:textAppearance="?android:attr/textAppearanceSmall" />
        </GridLayout>
    </android.support.v7.widget.CardView>

    <TextView
        android:id="@+id/titulo_contrasena"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingBottom="@dimen/espacio_norma_2"
        android:paddingTop="@dimen/espacio_norma_1"
        android:text="@string/etiqueta_contrasena"
        android:textAppearance="?android:attr/textAppearanceSmall" />

    <android.support.v7.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        card_view:cardCornerRadius="2dp"
        card_view:cardUseCompatPadding="true">

        <RelativeLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:padding="@dimen/espacio_norma_1">

            <ImageView
                android:id="@+id/icono_password"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentLeft="true"
                android:layout_centerVertical="true"
                android:layout_gravity="center"
                android:layout_marginRight="8dp"
                android:src="@drawable/contrasena" />

            <TextView
                android:id="@+id/texto_password"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:layout_gravity="center_vertical"
                android:layout_toRightOf="@+id/icono_password"
                android:text="@string/texto_cambiar_contrasena"
                android:textAppearance="?android:attr/textAppearanceSmall" />

            <ImageView
                android:id="@+id/icono_indicador_derecho"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:layout_centerVertical="true"
                android:src="@drawable/indicador_derecho" />

        </RelativeLayout>
    </android.support.v7.widget.CardView>
</LinearLayout>

En el momento en que añadamos las pestañas este fragmento luciría de la siguiente forma:

Perfil En Aplicación Android Con Material Design

5. Similar al paso anterior, esta vez diseñaremos el fragmento para las direcciones basándonos en el boceto.

Wireframe Lista De Direcciones Aplicación Android
Debido a que es una lista, necesitamos crear un layout para el fragmento con un recycler view. Algo tan sencillo como:

fragmento_grupo_items.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/reciclador"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scrollbars="vertical" />

El layout de los ítems no es tan complicado. Solo son una serie de cuatro textos verticales con los datos de la dirección, el departamento, ciudad y teléfono.

Adicionalmente se añade un icono para decirle al usuario que cada ítem es editable.

item_lista_direcciones.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="@dimen/espacio_norma_1">

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

        <TextView
            android:id="@+id/texto_direccion"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Dirección"
            android:textAppearance="?android:attr/textAppearanceSmall"
            android:textStyle="bold" />

        <TextView
            android:id="@+id/texto_departamento"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Departamento"
            android:textAppearance="?android:attr/textAppearanceSmall" />

        <TextView
            android:id="@+id/texto_ciudad"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Ciudad"
            android:textAppearance="?android:attr/textAppearanceSmall" />

        <TextView
            android:id="@+id/texto_telefono"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Teléfono"
            android:textAppearance="?android:attr/textAppearanceSmall" />
    </LinearLayout>

    <ImageView
        android:id="@+id/icono_indicador_derecho"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentEnd="true"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:src="@drawable/indicador_derecho" />
</RelativeLayout>

Lo siguiente es crear el adaptador para las direcciones. Debido a que este fragmento es muy sencillo, podemos incluir la fuente de datos como clase interna.

AdaptadorDirecciones.java

import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import com.herprogramacion.restaurantericoparico.R;

import java.util.ArrayList;
import java.util.List;

/**
 * Adaptador para poblar la lista de direcciones de la sección "Mi Cuenta"
 */
public class AdaptadorDirecciones
        extends RecyclerView.Adapter<AdaptadorDirecciones.ViewHolder> {


    public static class ViewHolder extends RecyclerView.ViewHolder {
        // Campos respectivos de un item
        public TextView direccion;
        public TextView departamento;
        public TextView ciudad;
        public TextView telefono;

        public ViewHolder(View v) {
            super(v);
            direccion = (TextView) v.findViewById(R.id.texto_direccion);
            departamento = (TextView) v.findViewById(R.id.texto_departamento);
            ciudad = (TextView) v.findViewById(R.id.texto_ciudad);
            telefono = (TextView) v.findViewById(R.id.texto_telefono);
        }
    }


    public AdaptadorDirecciones() {
    }

    @Override
    public int getItemCount() {
        return Direccion.DIRECCIONES.size();
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
        View v = LayoutInflater.from(viewGroup.getContext())
                .inflate(R.layout.item_lista_direccion, viewGroup, false);
        return new ViewHolder(v);
    }

    @Override
    public void onBindViewHolder(ViewHolder viewHolder, int i) {
        Direccion item = Direccion.DIRECCIONES.get(i);
        viewHolder.direccion.setText(item.numeroDireccion);
        viewHolder.departamento.setText(item.departamento);
        viewHolder.ciudad.setText(item.ciudad);
        viewHolder.telefono.setText(item.telefono);
    }

    /**
     * Modelo de datos para probar el adaptador
     */
    public static class Direccion {
        public String numeroDireccion;
        public String departamento;
        public String ciudad;
        public String telefono;

        public Direccion(String numeroDireccion, String departamento,
                         String ciudad, String telefono) {
            this.numeroDireccion = numeroDireccion;
            this.departamento = departamento;
            this.ciudad = ciudad;
            this.telefono = telefono;
        }

        public final static List<Direccion> DIRECCIONES = new ArrayList<Direccion>();

        static {
            DIRECCIONES.add(new Direccion("Cra 24 #2C-50", "Valle", "Cali", "3459821"));
            DIRECCIONES.add(new Direccion("Calle 100 Trans. 23", "Valle", "Cali", "4992600"));
            DIRECCIONES.add(new Direccion("Ave. 3ra N. #20-10", "Valle", "Cali", "4400725"));
        }
    }


}

Un detalle adicional. Los recycler views se pueden estilizar con la subclase RecyclerView.ItemDecoration. Esta permite recrear la forma en que son dibujados los elementos de la lista.

El boceto mostraba que usaríamos líneas divisorias para diferenciar los elementos, por lo que crearemos una decoración sencilla para este caso.

En primer lugar crea un nuevo drawable XML con un figura linear como se muestra en la siguiente definición.

linea_divisoria.xml

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

    <size
        android:width="1dp"
        android:height="1dp" />

    <solid android:color="@android:color/darker_gray" />

</shape>

Ahora crea una nueva clase que extienda de ItemDecoration y modifica su método onDrawOver() para renderizar el drawable desde el extremo izquierdo hacia el derecho:

DecoracionLineaDivisoria.java

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.RecyclerView;
import android.view.View;

import com.herprogramacion.restaurantericoparico.R;

/**
 * ItemDecoration personalizado para dibujar la linea drawable/linea_divisoria.xml en los
 * elementos de un recycler view
 */
public class DecoracionLineaDivisoria extends RecyclerView.ItemDecoration {
    private Drawable lineaDivisoria;

    public DecoracionLineaDivisoria(Context context) {
        lineaDivisoria = ContextCompat.getDrawable(context, R.drawable.linea_divisoria);
    }

    @Override
    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
        int left = parent.getPaddingLeft();
        int right = parent.getWidth() - parent.getPaddingRight();

        int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = parent.getChildAt(i);

            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();

            int top = child.getBottom() + params.bottomMargin;
            int bottom = top + lineaDivisoria.getIntrinsicHeight();

            lineaDivisoria.setBounds(left, top, right, bottom);
            lineaDivisoria.draw(c);
        }
    }
}

Finalmente dentro de FragmentoDirecciones recupera el recycler view y añade un nuevo adaptador junto a la decoración.

FragmentoDirecciones.java

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.herprogramacion.restaurantericoparico.R;


/**
 * Fragmento para la pestaña "DIRECCIONES" De la sección "Mi Cuenta"
 */
public class FragmentoDirecciones extends Fragment {

    private LinearLayoutManager linearLayout;

    public FragmentoDirecciones() {

    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

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

        RecyclerView reciclador = (RecyclerView)view.findViewById(R.id.reciclador);
        linearLayout = new LinearLayoutManager(getActivity());
        reciclador.setLayoutManager(linearLayout);

        AdaptadorDirecciones adaptador = new AdaptadorDirecciones();
        reciclador.setAdapter(adaptador);
        reciclador.addItemDecoration(new DecoracionLineaDivisoria(getActivity()));

        return view;
    }


}

Al momento de coordinar las pestañas con las páginas el resultado para este fragmento debería ser el siguiente:

Direcciones En Aplicación Android

6. Ahora es el turno del fragmento de la sección TARJETAS. Se supone que este contiene la lista de las tarjetas que ha usado el usuario para adquirir comidas a domicilio. Sin embargo en este caso asumiremos que no existe ninguna, por lo que el boceto mostrará un mensaje asociado a la situación.

WireFrame Aplicación Android Tarjetas De Crédito

Este diseño es el más simple. Solo añade un nuevo fragmento que infle el layout de un ImageView + un TextView (o un text view compuesto) y listo.

FragmentoTarjetas.java

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

import com.herprogramacion.restaurantericoparico.R;

/**
 * Fragmento para la pestaña "TARJETAS" de la sección "Mi Cuenta"
 */
public class FragmentoTarjetas extends Fragment {

    public FragmentoTarjetas() {
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragmento_tarjetas, container, false);
    }


}

El diseño sería el siguiente:

fragmento_tarjetas.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"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/imagen_no_tarjeta"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_gravity="center_horizontal"
        android:src="@drawable/tarjeta_credito" />

    <TextView
        android:id="@+id/texto_no_tarjeta"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="@string/texto_no_tarjetas"
        android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>

Este pequeño código produciría el siguiente aspecto del fragmento:

Tarjetas En Aplicación Android

7. Finalmente abre FragmentoCuenta.java y obtén una instancia y:

  • Obtén una instancia del ViewPager del fragmento.
  • Crea un FragmentStatePagerAdapter para poblar el pager.
  • Añade una instancia de cada fragmento creado anteriormente.
  • Usa el método setupWithViewPager() para coordinar las pestañas y páginas.

FragmentoCuenta.java

import android.graphics.Color;
import android.os.Bundle;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.herprogramacion.restaurantericoparico.R;

import java.util.ArrayList;
import java.util.List;


/**
 * Fragmento de la sección "Mi Cuenta"
 */
public class FragmentoCuenta extends Fragment {

    private AppBarLayout appBar;
    private TabLayout pestanas;
    private ViewPager viewPager;

    public FragmentoCuenta() {
    }

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

        if (savedInstanceState == null) {
            insertarTabs(container);

            // Setear adaptador al viewpager.
            viewPager = (ViewPager) view.findViewById(R.id.view_pager_mi_cuenta);
            poblarViewPager(viewPager);
            pestanas.setupWithViewPager(viewPager);
        }

        return view;
    }

    private void insertarTabs(ViewGroup container) {
        View padre = (View) container.getParent();
        appBar = (AppBarLayout) padre.findViewById(R.id.appbar);
        pestanas = new TabLayout(getActivity());
        pestanas.setTabTextColors(Color.parseColor("#FFFFFF"), Color.parseColor("#FFFFFF"));
        appBar.addView(pestanas);
    }

    private void poblarViewPager(ViewPager viewPager) {
        AdaptadorSecciones adapter = new AdaptadorSecciones(getFragmentManager());
        adapter.addFragment(new FragmentoPerfil(), getString(R.string.titulo_tab_perfil));
        adapter.addFragment(new FragmentoDirecciones(), getString(R.string.titulo_tab_direcciones));
        adapter.addFragment(new FragmentoTarjetas(), getString(R.string.titulo_tab_tarjetas));
        viewPager.setAdapter(adapter);
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        appBar.removeView(pestanas);
    }

    /**
     * Un {@link FragmentStatePagerAdapter} que gestiona las secciones, fragmentos y
     * títulos de las pestañas
     */
    public class AdaptadorSecciones extends FragmentStatePagerAdapter {
        private final List<Fragment> fragmentos = new ArrayList<>();
        private final List<String> titulosFragmentos = new ArrayList<>();

        public AdaptadorSecciones(FragmentManager fm) {
            super(fm);
        }

        @Override
        public android.support.v4.app.Fragment getItem(int position) {
            return fragmentos.get(position);
        }

        @Override
        public int getCount() {
            return fragmentos.size();
        }

        public void addFragment(android.support.v4.app.Fragment fragment, String title) {
            fragmentos.add(fragment);
            titulosFragmentos.add(title);
        }

        @Override
        public CharSequence getPageTitle(int position) {
            return titulosFragmentos.get(position);
        }
    }
}

Recuerda que si el contenido de los fragmentos asociados a las pestañas varía de continuamente es mejor usar la clase FragmentStatePagerAdapter que FragmentPagerAdapter.

El primero recicla las vistas cada vez que el ciclo de vida del fragmento acaba, lo que facilitará el traspaso entre las opciones del navigation drawer sin alterar el contenido.

Para finalizar esta sección ejecuta el proyecto desarrollado hasta el momento y comprueba el funcionamiento de la sección Mi cuenta.

#6. Tabs Con Grids En Fragmentos Similares

La sección Categorías es similar a Mi Cuenta, ya que tenemos tabs asociadas a un fragmento. Solo que esta vez el contenido se define por grids de comidas segmentadas en categorías.

Wireframe Aplicación Android Con Tabs y Grids

Al contener las pestañas elementos uniformes, el desarrollo se nos facilita mucho, ya que solo necesitamos un adaptador que se alimente de una fuente de datos personalizada, pero con la misma estructura de información.

Para llevar a cabo esta implementación requeriremos:

  • Un fragmento para la sección Categorías.
  • Un fragmento genérico que represente el contenido de todas las categorías.
  • Un layout para el diseño de los ítems pertenecientes a los grids.
  • Un adaptador para los ítems en los grids.

Pasemos a la acción…

1. Crea un nuevo fragmento llamado FragmentoCategorias.java e infla su contenido desde el layout existente fragmento_paginado.xml.

2. Copia y pega exactamente el mismo código del FragmentoCuenta.java en su interior excepto el cuerpo del método poblarViewPager(). Recuerda que usaremos fragmentos distintos para la creación de los grids por cada sección.

3. Por segunda vez añade un fragmento nuevo y nómbralo FragmentoCategoria.java. Debido a que usaremos un recycler view para generar las grillas, podemos reutilizar el layout fragmento_grupo_items.xml para inflar este fragmento.

4. Añade a la clase Comida.java tres nuevas listas para representar los ítems proyectados en las grillas. Sus nombres serán PLATILLOS, BEBIDAS y POSTRES como en los títulos de las pestañas.

Dentro de Comida.java

public static final List<Comida> BEBIDAS = new ArrayList<>();
public static final List<Comida> POSTRES = new ArrayList<>();
public static final List<Comida> PLATILLOS = new ArrayList<>();

static {
    ...

    PLATILLOS.add(new Comida(5, "Camarones Tismados", R.drawable.camarones));
    PLATILLOS.add(new Comida(3.2f, "Rosca Herbárea", R.drawable.rosca));
    PLATILLOS.add(new Comida(12f, "Sushi Extremo", R.drawable.sushi));
    PLATILLOS.add(new Comida(9, "Sandwich Deli", R.drawable.sandwich));
    PLATILLOS.add(new Comida(34f, "Lomo De Cerdo Austral", R.drawable.lomo_cerdo));

    BEBIDAS.add(new Comida(3, "Taza de Café", R.drawable.cafe));
    BEBIDAS.add(new Comida(12, "Coctel Tronchatoro", R.drawable.coctel));
    BEBIDAS.add(new Comida(5, "Jugo Natural", R.drawable.jugo_natural));
    BEBIDAS.add(new Comida(24, "Coctel Jordano", R.drawable.coctel_jordano));
    BEBIDAS.add(new Comida(30, "Botella Vino Tinto Darius", R.drawable.vino_tinto));

    POSTRES.add(new Comida(2, "Postre De Vainilla", R.drawable.postre_vainilla));
    POSTRES.add(new Comida(3, "Flan Celestial", R.drawable.flan_celestial));
    POSTRES.add(new Comida(2.5f, "Cupcake Festival", R.drawable.cupcakes_festival));
    POSTRES.add(new Comida(4, "Pastel De Fresa", R.drawable.pastel_fresa));
    POSTRES.add(new Comida(5, "Muffin Amoroso", R.drawable.muffin_amoroso));
}

5. Lo siguiente es crear un diseño para los ítems del grid. En este caso usaremos la modalidad footer con doble línea como se muestra en la documentación y en el boceto.

Formato Two Line Grid List En Material Design

El layout se compone de la imagen superior de cada comida con una altura predeterminada a 192dp. El footer será un LinearLayout con dos Text Views, donde la primer línea será el precio y la segunda el nombre de la comida.

item_lista_categorias.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:orientation="vertical"
    android:padding="1dp">

    <ImageView
        android:id="@+id/miniatura_comida"
        android:layout_width="match_parent"
        android:layout_height="192dp" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/accentColor"
        android:orientation="vertical"
        android:padding="@dimen/espacio_norma_1">

        <TextView
            android:id="@+id/precio_comida"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Precio"
            android:textAppearance="@style/TextAppearance.AppCompat.Subhead.Inverse"
            android:textStyle="normal|bold" />

        <TextView
            android:id="@+id/nombre_comida"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Nombre"
            android:textAppearance="@style/TextAppearance.AppCompat.Subhead.Inverse"
            android:textSize="12sp" />

    </LinearLayout>
</LinearLayout>

Más adelante este diseño produciría el siguiente aspecto visual:

Item Grid List Aplicación Android Restaurante
6. Crea un adaptador para los elementos que permita recibir como parámetro una lista de ítems genéricos. Esto con el fin de crear las tres variaciones de las categorías.

AdaptadorCategorias.java

import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import com.bumptech.glide.Glide;
import com.herprogramacion.restaurantericoparico.R;
import com.herprogramacion.restaurantericoparico.modelo.Comida;

import java.util.List;

/**
 * Adaptador para comidas usadas en la sección "Categorías"
 */
public class AdaptadorCategorias
        extends RecyclerView.Adapter<AdaptadorCategorias.ViewHolder> {


    private final List<Comida> items;

    public static class ViewHolder extends RecyclerView.ViewHolder {
        // Campos respectivos de un item
        public TextView nombre;
        public TextView precio;
        public ImageView imagen;

        public ViewHolder(View v) {
            super(v);

            nombre = (TextView) v.findViewById(R.id.nombre_comida);
            precio = (TextView) v.findViewById(R.id.precio_comida);
            imagen = (ImageView) v.findViewById(R.id.miniatura_comida);
        }
    }


    public AdaptadorCategorias(List<Comida> items) {
        this.items = items;
    }

    @Override
    public int getItemCount() {
        return items.size();
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
        View v = LayoutInflater.from(viewGroup.getContext())
                .inflate(R.layout.item_lista_categorias, viewGroup, false);
        return new ViewHolder(v);
    }

    @Override
    public void onBindViewHolder(ViewHolder viewHolder, int i) {
        Comida item = items.get(i);

        Glide.with(viewHolder.itemView.getContext())
                .load(item.getIdDrawable())
                .centerCrop()
                .into(viewHolder.imagen);
        viewHolder.nombre.setText(item.getNombre());
        viewHolder.precio.setText("$" + item.getPrecio());

    }


}

7. Abre de nuevo FragmentoCategoria.java y añade el siguiente código.

FragmentoCategoria.java

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.herprogramacion.restaurantericoparico.R;
import com.herprogramacion.restaurantericoparico.modelo.Comida;

/**
 * Fragmento que representa el contenido de cada pestaña dentro de la sección "Categorías"
 */
public class FragmentoCategoria extends Fragment {

    private static final String INDICE_SECCION
            = "com.restaurantericoparico.FragmentoCategoriasTab.extra.INDICE_SECCION";

    private RecyclerView reciclador;
    private GridLayoutManager layoutManager;
    private AdaptadorCategorias adaptador;

    public static FragmentoCategoria nuevaInstancia(int indiceSeccion) {
        FragmentoCategoria fragment = new FragmentoCategoria();
        Bundle args = new Bundle();
        args.putInt(INDICE_SECCION, indiceSeccion);
        fragment.setArguments(args);
        return fragment;
    }

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

        reciclador = (RecyclerView) view.findViewById(R.id.reciclador);
        layoutManager = new GridLayoutManager(getActivity(), 2);
        reciclador.setLayoutManager(layoutManager);

        int indiceSeccion = getArguments().getInt(INDICE_SECCION);

        switch (indiceSeccion) {
            case 0:
                adaptador = new AdaptadorCategorias(Comida.PLATILLOS);
                break;
            case 1:
                adaptador = new AdaptadorCategorias(Comida.BEBIDAS);
                break;
            case 2:
                adaptador = new AdaptadorCategorias(Comida.POSTRES);
                break;
        }

        reciclador.setAdapter(adaptador);

        return view;
    }

}

Las piezas de código tienen como fin la personalización del recycler view dependiendo de la categoría.

Al principio encontrarás la clave de un extra que representa el índice de la pestaña para la cual se creará el fragmento.

El método de clase nuevaInstancia() permite crear una nueva instancia del fragmento basado en el índice de la sección como argumento.

Dicho argumento es obtenido en el método onCreateView() para determinar con un switch que fuente de datos irá en el constructor del adaptador. Luego de la decisión, el adaptador es asignado al recycler view que se creó.

Recuerda que si deseas crear un Grid List con el RecyclerView es necesario usar un GridLayoutManager en la inicialización. El constructor de este elemento recibe el contexto y la cantidad de columnas que habrá (en este caso 2).

8. Finalmente puedes modificar el método poblarViewPager() de FragmentoCategorias añadiendo tres instancias con índices del 0 al 2 para satisfacer la estructura de las pestañas.

Dentro de FragmentoCategorias.java

private void poblarViewPager(ViewPager viewPager) {
    AdaptadorSecciones adapter = new AdaptadorSecciones(getFragmentManager());
    adapter.addFragment(FragmentoCategoria.nuevaInstancia(0), getString(R.string.titulo_tab_platillos));
    adapter.addFragment(FragmentoCategoria.nuevaInstancia(1), getString(R.string.titulo_tab_bebidas));
    adapter.addFragment(FragmentoCategoria.nuevaInstancia(2), getString(R.string.titulo_tab_postres));
    viewPager.setAdapter(adapter);
}

Si deseas añadir un action button de búsqueda dentro de la Toolbar, entonces crea el siguiente recurso de menú.

menu_categorias.xml

<menu 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"
    tools:context=".ActividadPrincipal">
    <item
        android:id="@+id/action_settings"
        android:orderInCategory="100"
        android:title="@string/action_settings"
        app:showAsAction="never" />
    <item
        android:id="@+id/action_search"
        android:icon="@drawable/busqueda"
        android:orderInCategory="1"
        android:title="@string/action_search"
        app:showAsAction="ifRoom" />
</menu>

Y ahora habilita la contribución del fragmento a la action bar con el método setHasOptionsMenu() dentro de onCreate() y luego infla el recurso dentro de onCreateOptionsMenu().

Dentro de FragmentoCategorias.java

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setHasOptionsMenu(true);
}

@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    inflater.inflate(R.menu.menu_categorias, menu);
}

Si ejecutas la aplicación tendrás el siguiente resultado:

Ejemplo Aplicación Android Con Menú De Comidas

#7. Iniciar Actividad Desde Navigation Drawer

Ya para finalizar nuestro experimento veremos cómo iniciar una actividad de ajustes desde el navigation drawer. Según el boceto Rico Pa’ Rico tiene una sola preferencia relacionada con la configuración de la frecuencia de notificaciones por correo. Esta a su vez se encuentra dentro de la categoría “Notificaciones”.

Wireframe De Actividad De Configuración En Android

A simple vista se reconoce que esta preferencia es del tipo ListPreference como vimos en el artículo ¿Cómo Crear Una Actividad De Preferencias En Android?. Donde se selecciona una de las opciones mostradas en un dialogo para tomar una decisión dentro de la aplicación.

La creación de esta actividad solamente requiere:

  • Una actividad con Toolbar junto a su respectivo layout.
  • Un fragmento de preferencias.
  • Un archivo strings.xml para preferencias.
  • Un recurso xml para mapear las preferencias que se inflarán en el fragmento.

Continuemos…

1. Crea una nueva actividad llamada ActividadConfiguracion.java junto a un layout que contenga una Toolbar y un contenido central.

actividad_configuracion.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" />

    <FrameLayout
        android:id="@+id/contenedor_configuracion"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />
</LinearLayout>

2. Dentro de la actividad añade un fragmento como miembro y extiéndelo de la clase PreferenceFragment. Solo necesitamos que en su método onCreate() se inflen las preferencias a través de addPreferencesFromResource().

ActividadConfiguracion.java

import android.app.FragmentTransaction;
import android.os.Bundle;
import android.preference.PreferenceFragment;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;

import com.herprogramacion.restaurantericoparico.R;

/**
 * Actividad para la configuración de preferencias
 */

public class ActividadConfiguracion extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.actividad_configuracion);

        FragmentTransaction ft = getFragmentManager().beginTransaction();
        ft.add(R.id.contenedor_configuracion, new FragmentoConfiguracion());
        ft.commit();

        agregarToolbar();
    }

    private void agregarToolbar() {
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        final ActionBar ab = getSupportActionBar();
        if (ab != null) {
            ab.setDisplayHomeAsUpEnabled(true);
        }
    }

    public static class FragmentoConfiguracion extends PreferenceFragment {

        public FragmentoConfiguracion() {
            // Constructor Por Defecto
        }

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            addPreferencesFromResource(R.xml.preferencias);
        }
    }
}

3. Ve a res/values y crea una nuevo recursos de strings con el nombre de strings_actividad_configuracion.xml y añade los textos necesarios que se ven en el boceto para proyectar la preferencia.

strings_actividad_configuracion.xml

<resources>
    <string name="titulo_actividad_configuracion">Configuración</string>

    <string name="categoria_notificaciones">Notificaciones</string>

    <string name="titulo_preferencia">Frecuencia de notificaciones por correo</string>
    <string name="key_preferencia">lista_frecuencias</string>
    <string-array name="lista_frecuencia">
        <item>Un correo diario</item>
        <item>Un correo a la semana</item>
        <item>Un correo al mes</item>
        <item>No quiero recibir correos</item>
    </string-array>
    <string-array name="lista_valores_frecuencia">
        <item>0</item>
        <item>1</item>
        <item>2</item>
        <item>3</item>
    </string-array>

</resources>

El recurso anterior contiene el nombre de la actividad de preferencias, el nombre de la categoría. También el título de la preferencia de lista, su clave (key) y dos arrays para representar el texto de las opciones y sus valores correspondientes.

4. Crea un nuevo recurso dentro de la carpeta xml (si no existe, créala) con un nodo PreferenceScreen. Luego añade una categoría junto a la ListPreference que ya veníamos planeando.

preferencias.xml

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">

    <PreferenceCategory
        android:key="categoria_notificaciones"
        android:title="@string/categoria_notificaciones">
        <ListPreference
            android:defaultValue="0"
            android:entries="@array/lista_frecuencia"
            android:entryValues="@array/lista_valores_frecuencia"
            android:key="@string/key_preferencia"
            android:negativeButtonText="@null"
            android:positiveButtonText="@null"
            android:summary="%s"
            android:title="@string/titulo_preferencia" />
    </PreferenceCategory>
</PreferenceScreen>

5. Modifica el estilo de la actividad principal en el Android Manifest a @style/Theme.ConNavigationDrawer. Esto evitará que la status bar se vea translucida debido al efecto que se usa en la actividad principal para seguir las especificaciones del navigation drawer.

AndroidManifest.xml

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/Theme.RicoPaRico" >
        <activity
            android:name=".ui.ActividadPrincipal"
            android:label="@string/app_name"
            android:theme="@style/Theme.ConNavigationDrawer">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".ui.ActividadConfiguracion"
            android:label="@string/titulo_actividad_configuracion"
            android:parentActivityName=".ui.ActividadPrincipal" >
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value=".ui.ActividadPrincipal" />
        </activity>
    </application>

</manifest>

6. Dentro de ActividadPrincipal.java usa el  método startActivity() para iniciar nuestra actividad de configuración. Recuerda que esto va sobre el método seleccionarItem() y el identificador de la sección de configuración es R.id.item_configuracion.

Dentro de ActividadPrincipal.java

private void seleccionarItem(MenuItem itemDrawer) {
    Fragment fragmentoGenerico = null;
    FragmentManager fragmentManager = getSupportFragmentManager();

    switch (itemDrawer.getItemId()) {
        case R.id.item_inicio:
            fragmentoGenerico = new FragmentoInicio();
            break;
        case R.id.item_cuenta:
            fragmentoGenerico = new FragmentoCuenta();
            break;
        case R.id.item_categorias:
            fragmentoGenerico = new FragmentoCategorias();
            break;
        case R.id.item_configuracion:
            startActivity(new Intent(this, ActividadConfiguracion.class));
            break;
    }
    if (fragmentoGenerico != null) {
        fragmentManager
                .beginTransaction()
                .replace(R.id.contenedor_principal, fragmentoGenerico)
                .commit();
    }

    // Setear título actual
    setTitle(itemDrawer.getTitle());
}

Si todo salió bien, cuando ejecutes la aplicación y selecciones la sección de configuración, se iniciará nuestra actividad y podremos variar el valor de la preferencia de notificaciones.

Iniciar Una Actividad Desde Un Navigation Drawer

Conclusión

El patrón recomendado por Google para usar dos niveles de navegación es el de Navigation Drawer y Tabs.

Este componente brinda flexibilidad y organización a la hora de crear secciones que ayuden al usuario a tener una visión global de la aplicación.

Vimos cómo usar varios tipos de layouts para generar un segundo nivel de navegación,basados en elemento como listas, grid views y pestañas.

La aplicación Rico Pa’ Rico permitió usar diferentes fragmentos para las secciones  del navigation drawer. Esta vez nuestro desarrollo fue dinámico gracias a los fragmentos anidados.

Como se mencionó arriba, también es posible crear una navigation drawer con diferentes actividades. Pero este es un tema que será tratado individualmente.

Por ahora puedes ir pensando en alimentar esta plantilla con datos reales a través de un web service y sincronizar los datos locales con tu servidor.

  • Anonim0420

    Podrias explicarme esta parte:

    private static final String INDICE_SECCION
    = “com.restaurantericoparico.FragmentoCategoriasTab.extra.INDICE_SECCION”;

    NO entiendo el valor qe debe llevar el INDICE, porque estoy haciendo un proyecto con nombres y paquetes diferentes, por favor encerio, llevo dias en esto…..

    Saludos desde Mexico :)

  • Luis Gonzalez

    Buen dia, quisiera saber si alguien implemento esta aplicacion con datos reales, tipo un loguin, que se pueda hacer pedidos etc, gracias!!!

  • alferjc7

    Hola, alguien me podria explicar como se podria ir a al inicio o a otro fragment dando click en uno de los pedidos?

  • Jose Daniel Franco

    Tengo información en los fragmentos de las Tabs pero no puedo hacer scrolling, cómo puedo implementarlo?

  • Jeff Alexander Mejia

    Hola James, quiero preguntarle, estoy buscando la manera de hacer la opcion de busqueda (action search) pero no llego a ninguna parte.

  • Ulises

    hola una pregunta y si quiero a partir de una pestaña (tab) accionar el navigation drawer? es decir que en mi pestaña este el navigation drawer

  • Krizthian Enrique Arreola Reye

    Amigo seria posible que me pudieras ayudar, estoy haciendo una app con conexión remota pero el problema esta con el JSON en el cual se encodea mi consulta a la DB MySQL, ya logre hacer que se mande a llamar el objeto y lo que contiene pero a la hora de extraerlo en los Strings y setearlo en los textview nomas no cambian los datos….
    Anexo captura de mi aplicación en el momento de obtener los datos.
    Lo estoy haciendo en un Navigation View
    Espero y me puedas ayudar…

  • Daniel Peiró

    Hola,

    ¿Por qué usar fragmentos en lugar de actividades en el primer nivel? Si se usaran actividades la compatibilidad sería anterior a la v4.2

    pd: pido perdón si he planteado una tontería … hace una semana sólo que he empezado en nativo.

    Gracias!

  • pepe

    Hola , que sepas me ha encantado el tutorial, he aprendido mucho, buen trabajo.

    Solo que me gustaria que en cada categoria se pudiera pulsar en la imagen o en el texto y que te llevara a una activity solo de esa categoria.

    resumen: quiero poder hacer click en la imagen y abrir una activity. se que tengo que hacer el image.setOnclick………….
    pero donde?
    DONDE??????????XDD

    • Luis Gonzalez

      Me uno a la misma pregunta

    • Rafael

      idem… donde se hace?

    • Rafael

      idem… donde se hace?

  • Hola James.
    Primero de todo, darte las gracias como siempre por tus tutoriales, pues, a pesar de tener una dificultad alta para quien no es programador como “yo”, son de gran ayuda.

    Te sigo desde hace tiempo, y estoy creando mi aplicación a medida que estudio tus ejemplos. Estoy intentando crear una aplicación para un proyecto casero de IOT, donde a través del movil pueda ver mis sensores, dar de alta nuevos sensores , eliminar y modificar, todo ello con un login y registro previo.

    Para ello he implementado a partir de tus tutoriales “Consumir Un Servicio Web REST Desde Android” el login y registro con un servicio web remoto y he adaptado la “lista de contactos cambiándola por “lista de sensores” con las credenciales del usuario “ClaveAPi”.

    Hasta aquí todo funciona bien. He conseguido las conexiones con las bases de datos remotas, poblar las listas y demás. Ahora quisiera a partir de este ejemplo de “Navigation Drawer” englobar la aplicación en este tipo de diseño que me parece perfecto.

    Estoy encontrando problemas cuando intento implementar este diseño pues cuando ejecuto la aplicación y después de logearme correctamente, ésta salta a la Main Activity del Navigation Drawer pero se para “La aplicación se ha detenido”. En log me da un que detallo al final.
    Si me pudieras orientar de como resolver el error o aconsejarme como enfocar esta aplicación pues estoy parado en este error y por mucho que reviso no hay manera de encontrar la solución. No se si necesitas que envíe algún detalle más.

    Muchas gracias de antemano por tu ayuda-.
    Saludos
    Óscar
    Detalle de la estructura:
    (de momento solo he intentado que al abirr muestre la lista de mis sensores en el primer “item del menu lateral” que lo he llamado en lugar de home “Mis sensores”,)
    3 Packages.
    * adjunto foto
    1) wisenmenu (diseño Drawer)
    FragmentoInicio
    MenuActivity
    2) wisenregisterlogin
    Config
    login
    MainActivity
    ProfileActivity
    RegisterUserClass
    Wellcome ( Pantalla de bienvenida flash)
    3) wisentool (manejo de sensores, sería lo mismo que contactos de un usuareio)
    modelo
    provider
    sync
    ui
    utilidades
    web

    Error log
    Caused by: java.lang.RuntimeException: com.amg_eservices.wisenmenu.MenuActivity@1d26998f must implement OnFragmentInteractionListener
    at com.amg_eservices.wisenmenu.FragmentoInicio.onAttach(FragmentoInicio.java:84)
    at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1019)
    at android.support.v4.app.BackStackRecord.ensureFragmentsAreInitialized(BackStackRecord.java:1077)
    at android.support.v4.app.BackStackRecord.beginTransition(BackStackRecord.java:1032)
    at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:658)
    at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1617)
    at android.support.v4.app.FragmentController.execPendingActions(FragmentController.java:339)
    at android.support.v4.app.FragmentActivity.onStart(FragmentActivity.java:602)
    at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1234)
    at android.app.Activity.performStart(Activity.java:6329)
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2665)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2767)
    at android.app.ActivityThread.access$900(ActivityThread.java:177)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1449)
    at android.os.Handler.dispatchMessage(Handler.java:102)
    at android.os.Looper.loop(Looper.java:145)
    at android.app.ActivityThread.main(ActivityThread.java:5951)
    at java.lang.reflect.Method.invoke(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:372)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1400)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1195)

    • Hola compañero. Mandame el proyecto por la página de facebook: https://www.facebook.com/hermosaprogramacion/

      • Hola James.
        Ya te he enviado el proyecto en zip, son 9 megas. por el facebook como me has pedido. LA idea es que después de la pantalla de bienvenida, pase al registro/logeo, después el cliente selecciona logout o menu, si da a menu ir al entorno drawer design y dando a “tools” debería ir a “objetos ” que equivale a “contactos” de un usuario. Cualquier ayuda es bienvenida, desde ya muchas gracias. Un saludo. Óscar

  • Henri Gustave

    Hola gracias por el tutorial.

    Tengo un problema. Cuando intento agregar otro elemento (por ejemplo, clínica) en el menú principal que tiene el mismo contenido que “categorías”; que me puso de error al abrir la aplicación. Android comete un error en este nivel: “int = GetArguments indiceSeccion () getInt (INDICE_SECCION).” Como el cuadro

    ¿Me podría ayudar?

  • Henri Gustave

    Hola gracias por el tutorial.
    Tengo un problema. Cuando intento agregar otro elemento (por ejemplo, clínica) en el menú principal que tiene el mismo contenido que “categorías”; que me puso de error al abrir la aplicación. Android comete un error en este nivel: “int = GetArguments indiceSeccion () getInt (INDICE_SECCION).”
    ¿Me podría ayudar?

  • Gon Her

    James. Sabes, estoy necesitando una mano. Es infima, pero me vuelvo loco y no puedo cambiarle el color a los tap. al fondo. Estoy con un proyecto donde todo es transparente, pero esa parte la tengo en color. donde puedo setear el color? Muchas gracias. saludos.
    PD. no el texto y el indicador, esto está bien visible. ver imagen por favor.

    • Jose H.

      Se cambia en el colors.xml, el color de los tabs, de la imagen naranja, etc. prácticamente todo :)

  • Nelson Quispe

    Un saludo, agradecer al autor del post por el tiempo y conocimientos compartidos. Ando con un problema y quiero consultar, cual es la configuración necesaria para que se muestre de entrada el fragmento “Mi Cuenta”, es decir que al ejecutar la aplicación se muestre primeramente “Mi cuenta” en su primera pestaña “Perfil”. Gracias.

    • Nelson Quispe

      jajaja, ya lo resolvi. era muy obvio

  • Rafelcf

    La mejor solución para que se cierre la aplicación al rotar la pantalla, es anotar en el manifest:
    android:configChanges=”screenSize|orientation”
    y quedaria asi:

    Con esto se resuelve el problema, ni cierra la aplicacion ni al volver a rotar vuelve a la primera pestaña.
    Al menos a mi me funciona, espero haber sido de ayuda.

  • Sefin Francis

    puedo usted ayudarme esta aplicación usando datos JSON

  • Jaime Tellez

    Alguien sabe a que se debe el FC que se produce al rotar la pantalla del dispositivo? alguna manera de solucionarlo? gracias de antemano.

    • Jaime Tellez

      Este es el error que me salta en el LOGCAT :

      FATAL EXCEPTION: main

      Process: ufpso.jatt.ufpsocal, PID: 4166

      java.lang.RuntimeException: Unable to start activity ComponentInfo{ufpso.jatt.ufpsocal/ufpso.jatt.ufpsocal.UI.ActividadPrincipal}: java.lang.NullPointerException: Attempt to invoke virtual method ‘void android.support.design.widget.AppBarLayout.removeView(android.view.View)’ on a null object reference

    • Jaime Tellez

      FATAL EXCEPTION: main

      Process: herprogramacion.restaurantericoparico, PID: 4166

      java.lang.RuntimeException: Unable to start activity ComponentInfo{herprogramacion.restaurantericoparico/herprogramacion.restaurantericoparico.UI.ActividadPrincipal}: java.lang.NullPointerException: Attempt to invoke virtual method ‘void android.support.design.widget.AppBarLayout.removeView(android.view.View)’ on a null object reference

      • Compañero, pon una confición de si la appbar es null dentro del método onDestroyView() así:

        @Override

        public void onDestroyView() {

        super.onDestroyView();
        if(appBar!=null){
        appBar.removeView(pestanas);
        }

        }

        Prueba y me dices

  • Christian Sari

    Hola a todos, no se si alguien hizo que al hacer click en lo mas pedidos se pueda enviar a otro fragment con un detalle ampliado, no he logrado hacer eso, si alguien me da una guía se lo agradezco.

    Saludos.

    • Katherine González

      Yo también estoy intentando hacer lo mismo y no encuentro la manera, como llamas en el fragmento inicio otro fragmento con otros detalles (agregando listas o tabs o algo así), quiero saber si es posible.

      • Jose Daniel Franco

        No sé mucho del tema pero los fragmentos anidados podrían funcionar

        • Rafael

          Al final lo habeis conseguido?? Como se haría??? y donde se debe poner? es muy urgente por FAVOR. James, estás ahi?

          • Rafael

            @JamesRevelo:disqus Puedes ayudarnos con esto por favor?

          • Rafael

            @JamesRevelo:disqus Puedes ayudarnos con esto por favor?

        • Rafael

          Al final lo habeis conseguido?? Como se haría??? y donde se debe poner? es muy urgente por FAVOR. James, estás ahi?

      • Luis Troya

        Si quieren insertar otro fragment dentro de fragmento de los tabs, si no estoy mal, deben usar el getChildFragmentManager

  • gonza28

    Hola James. Hoy estoy necesitando una pequeña mano. Mis fragmentos muestran una lista estatica. es decir un modelo arrayList como estas usando qui mismo.

    Moblema y ruego me des una pequeña ayuda es la orientacion. Imaginate que en el primer item del menu drawer tengo lanzado un viewPager con 3 fragmentos.

    Segundo item del menu drawer lanzo otro viewPager con otros 3 fragmentos. Supongamos que el usuario está situado en el segundo item viendo uno de los 3 fragmentos. Si gira el telefono, la actividad carga el primer viewPager. es decir que vuelve al primer item y lo que yo necesito es que si pones en ORIENTATION_LANDSCAPE, la actividad siga mostrando el mismo viewPager. no importa los items del recycler, solo quiero que siga mostrando el fragmento donde estaba. podras darme un metodo por favor? un abrazo y muecha suerte. Y muchas gracias desde ya.

    • Vicky Vicent

      Hola gonza28, a lo mejor ya es un poco tarde para solucionar el problema, pero lo dejo por si mas gente se tropieza con este problema. Resulta que, al cambiar la orientación del dispositivo estando situado en una de las pestañas del segundo ítem del NavigationView (Mi Cuenta > Perfil), me aparecía la pantalla principal (Inicio) en vez de la que estaba viendo (Mi Cuenta > Perfil). Aquí van las soluciones a lo anterior y a lo del cierre de la App cuando cambias la orientación:

      SOLUCIÓN A QUE NO SE CIERRE LA APP CUANDO CAMBIE LA ORIENTACIÓN: quería aclarar que la solución dicha en comentarios anteriores si funciona y es tan simple como en el archivo FragmentoCuenta.java modificar lo siguiente:

      @Override
      public void onDestroyView() {
      super.onDestroyView();
      if(appBar != null){
      appBar.removeView(pestanas);
      }
      }

      Con ello ya no se cerrará la App, pero volverá al fragmento inicial.

      SOLUCIÓN A QUE NO VUELVA AL FRAGMENTO INICIAL UNA VEZ ROTADA LA PANTALLA: primero aclarar que no es ningún error. Si recordamos, en el tutorial, cuando rellenamos el método onCreate() de ActividadPrincipal.java añadimos el siguiente código:

      if (navigationView != null) {
      prepararDrawer(navigationView);
      // Seleccionar item por defecto
      seleccionarItem(navigationView.getMenu().getItem(0));
      }

      Pues la última línea del IF es la culpable de que volvamos a la pantalla principal una vez rotemos la pantalla. Ahora te preguntaras ¿por qué?, pues muy fácil, porque según el ciclo de vida de las aplicaciones Android, cuando rotamos la pantalla de nuestro dispositivo para ver la versión horizontal de nuestra App, esta aplicación en vertical se destruye por completo y se abre nuestra App con la versión horizontal y si en la última línea del IF le indicamos que la primera pantalla que tiene que mostrar va a ser la de Inicio, el lo hace, por eso nos sucede lo que nos sucede. ¿Solución? También es sencilla, simplemente tenemos que guardarnos de alguna forma la posición del ítem del NavigationView que estamos viendo. Yo, lo he solucionado con los métodos onSaveInstanceState() y onRestoreInstanceState() que sirven para este tipo de cosas. Te explico:

      El método onSaveInstanceState() sirve para guardar los datos que necesitemos rescatar. Al ser un bundle es muy fácil de utilizar. Aquí guardamos una variable item que contendrá un int que pertenecerá a un item del NavigationView. Esto lo podemos obtener añadiendo dentro de cada case del método seleccionarItem(), un item=0, en el siguiente case item=1, etc. Cabe decir que esta variable será global (“public int item”).

      @Override
      protected void onSaveInstanceState(Bundle outState) {
      super.onSaveInstanceState(outState);
      outState.putInt(“Frag”, item);
      }

      El método onRestoreInstanceState() es complementario del anterior y sirve para rescatar los datos que hayamos guardado antes en outState, por lo que solo tenemos que asignar el valor de “Frag”, que es el clave por la referirnos a la variable item, a item para restaurar el valor. Por ultimo, solo tendríamos que volver a llamar al método seleccionarItem() para que se solucione el problema.

      @Override
      protected void onRestoreInstanceState(Bundle savedInstanceState) {
      super.onRestoreInstanceState(savedInstanceState);
      item = savedInstanceState.getInt(“Frag”);
      seleccionarItem(navigationView.getMenu().getItem(item));
      }

      Esto es todo, siguiendo estos pasos solucionaras el error y el problema. Espero haberte ayudado, a ti a y cualquiera que tenga este problema. Yo lo he probado y funciona 100%.

      Un Saludo y gracias al autor por estas “clases” de Android, soy geniales.

    • Vicky Vicent

      Hola gonza28, a lo mejor ya es un poco tarde para solucionar el problema, pero lo dejo por si mas gente se tropieza con este problema. Resulta que, al cambiar la orientación del dispositivo estando situado en una de las pestañas del segundo ítem del NavigationView (Mi Cuenta > Perfil), me aparecía la pantalla principal (Inicio) en vez de la que estaba viendo (Mi Cuenta > Perfil). Aquí van las soluciones a lo anterior y a lo del cierre de la App cuando cambias la orientación:

      SOLUCIÓN A QUE NO SE CIERRE LA APP CUANDO CAMBIE LA ORIENTACIÓN: quería aclarar que la solución dicha en comentarios anteriores si funciona y es tan simple como en el archivo FragmentoCuenta.java modificar lo siguiente:

      @Override
      public void onDestroyView() {
      super.onDestroyView();
      if(appBar != null){
      appBar.removeView(pestanas);
      }
      }

      Con ello ya no se cerrará la App, pero volverá al fragmento inicial.

      SOLUCIÓN A QUE NO VUELVA AL FRAGMENTO INICIAL UNA VEZ ROTADA LA PANTALLA: primero aclarar que no es ningún error. Si recordamos, en el tutorial, cuando rellenamos el método onCreate() de ActividadPrincipal.java añadimos el siguiente código:

      if (navigationView != null) {
      prepararDrawer(navigationView);
      // Seleccionar item por defecto
      seleccionarItem(navigationView.getMenu().getItem(0));
      }

      Pues la última línea del IF es la culpable de que volvamos a la pantalla principal una vez rotemos la pantalla. Ahora te preguntaras ¿por qué?, pues muy fácil, porque según el ciclo de vida de las aplicaciones Android, cuando rotamos la pantalla de nuestro dispositivo para ver la versión horizontal de nuestra App, esta aplicación en vertical se destruye por completo y se abre nuestra App con la versión horizontal y si en la última línea del IF le indicamos que la primera pantalla que tiene que mostrar va a ser la de Inicio, el lo hace, por eso nos sucede lo que nos sucede. ¿Solución? También es sencilla, simplemente tenemos que guardarnos de alguna forma la posición del ítem del NavigationView que estamos viendo. Yo, lo he solucionado con los métodos onSaveInstanceState() y onRestoreInstanceState() que sirven para este tipo de cosas. Te explico:

      El método onSaveInstanceState() sirve para guardar los datos que necesitemos rescatar. Al ser un bundle es muy fácil de utilizar. Aquí guardamos una variable item que contendrá un int que pertenecerá a un item del NavigationView. Esto lo podemos obtener añadiendo dentro de cada case del método seleccionarItem(), un item=0, en el siguiente case item=1, etc. Cabe decir que esta variable será global (“public int item”).

      @Override
      protected void onSaveInstanceState(Bundle outState) {
      super.onSaveInstanceState(outState);
      outState.putInt(“Frag”, item);
      }

      El método onRestoreInstanceState() es complementario del anterior y sirve para rescatar los datos que hayamos guardado antes en outState, por lo que solo tenemos que asignar el valor de “Frag”, que es el clave por la referirnos a la variable item, a item para restaurar el valor. Por ultimo, solo tendríamos que volver a llamar al método seleccionarItem() para que se solucione el problema.

      @Override
      protected void onRestoreInstanceState(Bundle savedInstanceState) {
      super.onRestoreInstanceState(savedInstanceState);
      item = savedInstanceState.getInt(“Frag”);
      seleccionarItem(navigationView.getMenu().getItem(item));
      }

      Esto es todo, siguiendo estos pasos solucionaras el error y el problema. Espero haberte ayudado, a ti a y cualquiera que tenga este problema. Yo lo he probado y funciona 100%.

      Un Saludo y gracias al autor por estas “clases” de Android, soy geniales.

  • Manuel Alonso Talledo Rondoy

    Hola, tengo una consulta: Estoy haciendo algo parecido, pero en el primer fragment (Inicio en tu tutorial) tengo unos text view, editText,checkbox, boton, con los que realizaré unos cálculos con los datos ingresados. Pero al navegar a otro fragment y volver al del inicio, los datos que ya había ingresado se han borrado. ¿Cómo tengo que hacer para que los fragments no se vuelvan a crear, sino que queden tal como los dejé?

    • Hola Manuel, intenta usar FragmentPagerAdapter para conservar el estado de los fragmentos. Hazlo y me dices.

  • mauricio

    No logro encontrar en el codigo..en que lugar se une la cabecera_drawer.xml con el menu…. por que estoy tratando de cargar otra cabecera pero no encuentro en que parte del codigo lo haces …ya que el cabecera_drawer no tiene un id ni nada… no se como se agrega por arte de magia xD! …

  • mauricio

    Hola James que tal..pregunta cortita ojala me puedas ayudar.. en FragmentoCategoria.java tienes un switch en donde cargas los platillos postres y bebidas de la siguiente forma adaptador = new AdaptadorCategorias(Comida.PLATILLOS); es aqui la pregunta…¿como puedo cargar un fragmento que es un recyclerview que se llena a traves de un json? …no se como implementarlo …ayuda please :)

  • Saamuel Camacho

    Hola buenas noches excelente tutorial! Pero una pregunta si quisiera pasar de la imagen de inició que contiene los platillos a otro activity como le podría hacer, te agradezco tu ayuda gracias

  • Bruno Galindo Narro

    James Revelo Urrea hola buen tutorial, tengo un problema cuando llego a la parte de 4 Sección De Navigation Drawer Con Fragmento De Lista cuando ejecuto y me aparece el principal layout no me muestra las imagenes ni la barra de lo mas pedido, cada vez que selecciono INICIO me sale un error

    11-15 20:52:27.276 7367-7387/com.example.hotd.drw E/Surface﹕ getSlotFromBufferLocked: unknown buffer: 0xa2ac70f0

    11-15 20:52:27.278 7367-7387/com.example.hotd.drw D/OpenGLRenderer﹕ endAllStagingAnimators on 0xa376e600 (ListPopupWindow$DropDownListView) with handle 0xa2c7d030

    11-15 20:52:54.149 7367-7376/com.example.hotd.drw W/art﹕ Suspending all threads took: 9.221ms

  • Bruno Galindo Narro

    @JamesRevelo:disqus he seguido el tutorial hasta la seccion de navigation drawer con fragmento de lista al ejecutar no me muestra las imagenes ni la barra de lo mas pedido, lo que me bota por error cada vez que selecciono Inicio es
    E/Surface﹕ getSlotFromBufferLocked: unknown buffer: 0xa2ac70f0

    ayudame porfavor D:

  • Hola amigos, gracias a sus post he podido mejorar mucho mi app, tengo una duda, es posible realizar el diseño de la imagen adjunta? estoy usando un tabLayout, me muestra los titulos [Inf. de usuario | Redes Sociales], pero no el contenido… el TabLayout solo funciona en los ActionBar? gracias!

  • Carlos Castillo Urrunaga

    Hola James,

    Eh implementado satisfactoriamente el tutorial.

    Sin embargo me inquieta una duda, y es que queria implentar el uso del navigationView en la ActividadConfiguracion atravez del icono drawer toggle. solo eh logrado colocar el drawer toggle pero no logro ver las opciones de menu.
    Es posible ver esa opcion en la ActividadConfiguracion, si es asi como se implementaria. en una actividad

    saludos,

  • Damian Morales

    Disculpa podrias pasarme las dimensiones que usaste para este proyecto

  • Manuel Alonso Talledo Rondoy

    Hola cuando pongo el equipo en modo horizontal, la aplicación de cierra. Cómo se puede arreglar eso?

    • Jaime Tellez

      pudiste solucionarlo amigo?

      • Hola compañeros, si mal no recuerdo un lector dijo que el error se daba por la app bar.

        Lo que toca hacer es poner una verificación así en el método onDestroyView():

        if( appbar !=null){
        //acciones
        }

        Verifiquen y me cuentan

        • Jaime Tellez

          si compañero, eso es! muchas gracias. Muchas veces he intentado algo, pero con poco éxito, que al rotar la pantalla como ocurre un onDestroy, lo que sucede es que me manda a la pantalla inicio de nuevo, en vez de dejarme en la misma pantalla actual, claro esta, sin usar trucos como en el manifest, pero no doy con el chiste. gracias por todos tus aportes!

  • German

    Hola James ,, te hago 2 consultas ,, 1 -como puedo hacer para agrandar el tamaño del texto de los tabs programaticamente , y 2 – se puede hacer que en el caso de tenerlo en modo scroll con varios tabs al seleccionar un tab este quede centrado ?? siempre el tab seleccionado quede centrado,,, saludos espero me puedas ayudar Gracias y muy bueno segui asi, Saludos.

    • Creo que programáticamente no existen métodos publicos para modificar el tamaño del texto. Pensaría que es mejor crear un estilo personalizado que herede de Widget.Design.TabLayout. No lo he probado pero supongo que puedes usar el atributo textSize o el que coincida para varias las medidas.

      No entiendo la segunda pregunta.

  • Samuel Alejandro Díaz

    Hola, me ha servido mucho el trabajo que has hecho, soy nuevo en android, me encontrado con problemas al momento de poblar el recyclerview desde mi base de datos, te agradecería si me pudieras ayudar con eso. Saludos :D

    • Hola Samuel, gracias por comentar.

      Ese tema debo expandirlo del articulo de recycler view que ya existe. Sin embargo estoy creando un nuevo articulo en el que es necesario incluir datos de un bd al recycler, creo que de allí puedes ver como se implementa.

      • Samuel Alejandro Díaz

        Muchas gracias por tu respuesta ! Ya logré poblar el recyclerview desde la base de datos, pero no hay mucha información en español, tu articulo será de utilidad para muchos

  • Cristopher

    Una consulta el boton de REGRESAR del ACTIVITY ActividadConfiguracion no funciona, sabes por que ?, asimismo en el ejemplo que adjuntaste no esta el evento que indique que retorne a la anterior actividad

  • Kryancelt Belmont

    Estoy haciendo un proyecto similiar pero con la api 15 y me estoy guiendo con tu trabajo pero a la hora de dependencias veo que son para versiones superiores:
    dependencies {

    compile ‘com.android.support:appcompat-v7:23.0.1’
    compile ‘com.android.support:design:23.0.1’
    compile ‘com.android.support:recyclerview-v7:23.0.1’
    compile ‘com.github.bumptech.glide:glide:3.6.1’
    compile ‘com.android.support:support-v4:23.0.1’
    compile ‘com.android.support:cardview-v7:23.0.1’
    }

    Hay alguna manera de incorpararlas a mi proyecto ya que estoy trabajando con la api 15?

    • Intenta cambiar las de soporte de :23 a :15

      • Kryancelt Belmont

        supongo que no hay soprte para esas ya que me sale failed to resolve

        la unica que me funciono fue esta

        compile ‘com.github.bumptech.glide:glide:3.6.1’

  • Jose Delgado

    Hola James aunque indicastes que no es un tutorial de ninjamock, por favor respóndeme si se puede hacer las conexiones para el workflow desde el ninjamock o agarras las imágenes y utilizas un editor gráfico. Si se puede desde el ninjamock, déjame unas pequeña indicaciones o que editor usas. Gracias.

    • Hola Jose. Las conexiones las hago en creately.com. Sin embargo puedes usar cualquier editor. ninjamock.com al parecer no lo permite

  • Rafelcf

    Gracias por este tuto otra vez y otra duda, como lo hago para abrir un fragment desde el menu settings, los tres puntitos.

    public boolean onOptionsItemSelected(MenuItem item) {

    switch (item.getItemId()) {
    case R.id.action_settings:
    drawerLayout.openDrawer(GravityCompat.START);
    return true;
    case R.id.action_jornada1:
    startActivity(new Intent(this, Frag_J1.class));
    break;

    }
    return super.onOptionsItemSelected(item);

    • Con FragmentTransaction.replace():

      final FragmentTransaction ft = getFragmentManager().beginTransaction();
      ft.replace(R.id.details, new NewFragmentToReplace(), “NewFragmentTag”);
      ft.commit();

  • Raul Pairó Ferrer

    Buenos días, felicidades por el blog, podrías ayudarme a hacer que se muestren iconos en lugar de Strings en el titulo de las tabs? muchas gracias!

  • Robert Stark

    Hola James! enviando saludos a la distancia y agradeciendo tu magnifico trabajo enseñando!
    quería saber como podría un agregar un seekbar y/o un switch button a un recyclerView y sus eventos listeners … Desde ya te envío un gran abrazo! muchas gracias!

    • Hola Robert, gracias por comentar.

      Ambos elementos puedes añadirlos libremente en el layout de los items, eso no tiene problema.

      Las escuchas puedes asignarlas dentro de onBind dependiendo de la acción que vayas a realizar.

      Ahora, si al presionar cada item del recycler vas a generar un evento en la seekbar, entonces debes crear una interfaz de comunicación entre el recycler y la interfaz. Puedes ver más de como hacerlo en este artículo que escribi: foros.cristalab.com/tutorial-android-crear-listas-con-recyclerview-t115354/

      Con el evento onClick() podrias usar findViewById() para encontrar el componente y escribir las instrucciones correspondientes.

      • Robert Stark

        Hola James muchaas muchas gracias de verdad! Me resultó bastante bien :) me fue de mucha ayuda todo! … Eso si aprovechando la instancia me gustaría saber como podria hacer un recycler view en donde el diseño de los views fueran distintos, o sea, no siguieran un patron especifico … Desde ya enormemente agradecido de todo lo que has enseñado…

        • Hola Robert, no se si te refieras a la decoración. Eso se logra con la clase RecyclerView.ItemDecoration, en este artículo puedes ver un pequeño ejemplo para agregar una linea: http://www.hermosaprogramacion.com/2015/09/aplicacion-android-con-navigation-drawer-y-tabs/

          • Robert Stark

            hola james no sé si es exactamente eso, osea me refería a si en por ejemplo en un recyclerview el cual posee varios item views en una lista, en el cual tengo un itemview el cual se compone de una imagen, un text view y un botón, pero en otro view del recyclerview este no tiene una imagen si no que un rating bar un edit text y asi en lo sucesivo la idea es crear views los cuales no siguen un orden o patrón de diseño similar. saludos!

          • Robert Stark

            hola James a pesar de a ver visto no logro poder saber como hacerlo por favor si tienes alguna pequeña ayudita te lo agradecería un montón, please! la idea es que los items sean muy distintos en layout dentro de una lista, gracias =)

  • Juan Enrique

    Hola tengo problemas con el paso del Navigation Drawer al momento de asignar el toolbar me da un
    error aqui: setSupportActionBar(toolbar);

    con que target debo trabajar, o que debo hacer? segui todos los pasos tal cual estan en el tutorial, les agradecere mucho la ayuda.

    Saludos

    • Juan Enrique

      Ya lo solucione xD

  • Victor Alvarado

    Perfecto tutorial, me fue de mucha ayuda para implementar las Tabs con diferentes fragmentos.
    Solo tengo una consulta…
    Quiero que al hacer scroll en el gridview o recyclerview el Toolbar de oculte y solo queden las pestañas. Seguí los pasos de la documentacion de la Support library de Google pero no me funcionó. Tal vez puedas horientarme de como hacerlo siguiendo los lineamientos de este tutorial y usanso la Support Design library.

    Gracias!

    • Hola victor. Puedes agregar el coordinator layout en el layout de la actividad directamente para evitar agregarlo de forma programática en los fragmentos. Con eso esperaría que funcione.

      Sin embargo también puedes optar por crear actividades individuales en vez de fragmentos para tener un control de los layous y agregar el coordinator en donde se necesite.

      • Victor Alvarado

        Hola James, gracias por responder. Solucioné el problemita, lo que pasaba es que esstaba usando ListView en lugar de RecyclerView.

        Pero tengo una consulta; tengo 3 pestañas en mi app y quiero que al iniciar la app se la tab por defecto sea la 1 (o sea la de en medio)

  • Cristopher

    Excelente aporte, pero una consulta como haria ese menu dinámico, que carge de la BD, y además redireccione a los Fragment

    • Hola Cristopher. No se si te refieras al problema de usar el recycler view para poblarlo con una bd. En ese caso debes agregar una atributo del tipo Cursor al adaptador y luego usar sobrescribir el método onbind para que lea las columnas respectivas.

      Espero actualizar mi articulo sobre recycler view en algún momento para mostrar esa carácteristica.

      • Cristopher

        Una consulta, con el ejemplo que has subido he intentado de agregar 7 tabs mas, pero cuando hago click en el ultimo por ejemplo, este retorna al primer TAB, no una forma de que el TAB seleccionado se quede.

        • O sea cuando presionas la última tab se seleeciona la primera?, o se selecciona el primer fragmento?

          • Cristopher

            No se selecciona, pero visualmente se retorna al primer TAB, por ejemplo:
            Los 7 dias de la semana

            — Solo se visualiza LUNES – MARTES – MIERCOLES
            — Del otro lado estan JUEVES – VIERNES – SABADO – DOMINGO

            Si selecciono Domingo, se muestra la info, pero retorna a la parte donde esta LUNES – MARTES – MIERCOLES.

            Para que pruebes utiliza la fragment de direcciones y copialo 10 veces, cuando seleccionas el ultimo vas a ver que no se queda quieto

          • Puedes mostrar el código donde añades las tabs?

          • Cristopher

            String[] sNombreDia={“Lunes”,”Martes”, “Miércoles”,”Jueves”,”Viernes”,”Sábado”,”Domingo”};
            int iNumDia;

            for (iNumDia = 0; iNumDia < 7; iNumDia++){
            adapter.AddFragment(new FragmentoHorario(),sNombreDia[iNumDia]);
            }

            viewPager.setAdapter(adapter);

          • Cristopher

            He agredo un for para que vaya agregando, en este caso 7 veces el mismo fragment ( lista de horas ) por los 7 días de la semana

          • Asignaste el modo scrollable al tablayout? tabs.setTabMode(TabLayout.MODE_SCROLLABLE);

          • Cristopher

            SI gracias amigo, ya he corregido mi problema. Mas bien tengo un PROBLEMA MUCHO MAS GRANDE :(….. Quiero llamar un metodo Async ( Consultar Web Service ) desde uno de estos Fragment ( ejemplo Fragmento Direcciones ), pero no he podido lograrlo, solo he podido hacer desde un Activity. Sabes alguna solución

          • Mmm pero en si cual es el problema?, que error te sale?, estas usando una tarea asincrona para hacer la peticion http?

          • Cristopher

            Y otra consulta, como puedo REFRESAR un FRAGMENT, por que cuando quiero volver a llamar el mismo fragment no entra en el método de onCreate

          • Cristopher

            Para ver si existe otra forma he hecho esto:

            Ahora si se queda en el medio el TAB, pero se muestra el ViewPager, ahora se visualizan dos niveles :(

  • Simon K

    Buenas, genial tutorial, todo muy detallado, pero creo que está pensado para gente con un mínimo de nivel, no para novatos como yo. Tengo desarrollado un menú drawer, pero no entiendo muy bien el concepto de como introduces los tabs en los fragmentos, es un ejemplo muy avanzado para mí, ya que no consigo integrar correctamente tabs en mi menú. Para practicar mi idea era que en la primera opción del drawer cargara una pestaña(no hacer nada), dos en el segundo item, y así hasta el final del menú. Hablo de cargar fragmentos vacíos para ir rellenando el contenido poco a poco, ¿podrías ayudarme?

  • Cristopher

    Hola que tal, AYUDA HELPMEEE, he descargado el proyecto, pero me sale error:

    Error:A problem occurred configuring project ‘:app’.

    > Could not download glide.jar (com.github.bumptech.glide:glide:3.6.1)

    > Could not get resource ‘https://jcenter.bintray.com/com/github/bumptech/glide/glide/3.6.1/glide-3.6.1.jar’.

    > Could not GET ‘https://jcenter.bintray.com/com/github/bumptech/glide/glide/3.6.1/glide-3.6.1.jar’.

    > peer not authenticated

    • Creería que debes actualizar tu SDK

      • Cristopher

        Excelente, ya funciona. Otra consulta como haria ese menu dinámico ( navigation drawer ), que carge de un ArrayList, y además redireccione a los Fragment

        • Mmm pues viendo la definición de la clase no existe un método para hacerlo directamente. Supongo que debes obtener el menú con NavigationView.getMenu() y luego obtener y luego añadir los elementos con Menu.add() usando la lista.

  • Leandro

    Hola James, muy buen tutorial.

    Una duda, ¿cómo podría contraerse la Toolbar para que cuando se hace scroll sobre las categorías, las tabs se queden fijas arribas?

    En este caso, al ser fragmentos anidados, no encuentro la forma, habitualmente lo hago con un CoordinatorLayout y añadiendo “app:layout_scrollFlags=”scroll|enterAlways” sobre el Toolbar.

    • Bueno hay varias formas. Podrías añadir el coordinator por defecto al layout de la actividad para tenerlo a la mano en los casos que lo necesites. O también puedes insertarlo en tiempo real.

      O incluso podrías mejor usar solo actividades para tener el control dividido en layouts independientes.

  • Javier M

    Hola James, excelente tutorial cada vez más completos. Puedes orientarme de como lanzar una actividad detalle al seleccionar un elemento(platillo, bebida o postre) de la sección “Categorías”

  • MIGUEL EDUARDO COTES MENDEZ

    Hola, que buen tutorial, exactamente lo que estaba buscando…. Me gustaría saber como colocarle un subtitulo al menú como aparece en la imagen…. Gracias!

    • Hola Miguel. Se me viene a la mente usar el elemento actionViewClass para personalizar los items. La verdad no he probado mucho la personalización de los elementos del navigation drawer.

  • Victor Gracia

    Te doy la enhorabuena por el magnífico trabajo que realizas con estos tutoriales y en especial esta entrada, creo que haces un gran trabajo. Estoy empezando a incorporar el componente drawer a mis desarrollos android, y creo que ayuda bastante seguir tu entrada.
    Os invito a seguirme en mi blog y ayudaros con temas relacionados en el desarrollo android. Me llamo Víctor Gracia y soy desarrollador de aplicaciones android y web.

    http://www.victorgraciaweb.com

    • Gracias por comentar Victor. Esa es la idea, que las personas puedan usar los tutos en sus desarrollos, y si encuentran errores o mejores prácticas, entonces que nos lo hagan saber.

      Gracias por la recomendación, voy a ver tu web, ses ve interesante :D

      Saludos!

  • Gabriel Villarreal

    buen tutorial! como siempre tan explicativo y entendible. Te agradezco tu tiempo y empeño en lo que haces y el tiempo de contestar dudas. Muchas gracias!!

  • Diego Rueda

    Gracias Por el Aporte, me ayudo a resolver varias dudas

  • Cristopher

    Como puedo agregar subitem al menu ?

  • Cristopher

    Una consulta, cual deben ser las dimensiones de las imágenes, por que una vez quise agregar una imagen con resolución de 3022×1915 y el aplicativo se mostraba un error cuando compilaba.

  • Cecilia

    Gracias James ,, vas a hacer tuto de google closing Message con php y mysql ??? Gracias por la ayuda sos un grosos

  • Oscar Sequeiros

    Buen día James.
    ¡Gracias por el aporte! Es de mucha utilidad para nosotros.
    Tengo una consulta James, ¿trae alguna dificultad en términos de rendimiento tener varios fragmentos y solamente una actividad?

    • Quizás por la complejidad de los layouts que estos tengan. Pero en cuanto a su complejidad de creación, es mucho menor que la de una actividad.

      Todo depende de los propósitos que tengas en mente con tu app.

  • Gon Her

    Wow James. Muchas gracias compañero. My buen tutorial. Y gracias al anterior, se como poner separadores. Aunque no lo probé, puedo deducir que funciona. Muchas gracias por todo lo que haces. Sabes, dia a dia nos das la mano y eso es de agradecer.
    Que estés amigo. Un abrazo!

    • Hola Gon gracias por comentar. Creo que esto era lo que me pedías hace poco.

      Para la selección de los items se usa el elemento para los drawables.

      • Gon Her

        Pero donde lo declaras? Estoy feliz con este tutorial super completo de verdad. Puedo poner casi cuaqluier funcion en el menú y no solo lanzar fragmentos y actividades. es un espectaculo. muchas gracias James. Adiero a la consulta de arriba: Se podria poner un subTitle a los items? la verdad es que consulto de lleno por que está muy bueno el tuto. Te lo agradezco con el alma.

        Abrazo James

  • Rafelcf

    James perfecto y muchas gracias, tengo unas dudas, en mi cuenta, como puedo agregar un tab y me muestre un ListActivity?

    private void poblarViewPager(ViewPager viewPager) {
    AdaptadorSecciones adapter = new AdaptadorSecciones(getFragmentManager());
    adapter.addFragment(new FragmentoPerfil(), getString(R.string.titulo_tab_perfil));

    startActivity(new Intent(getActivity(), MiActivity.class));getString(R.string.titulo_tab_MiActivity);

    adapter.addFragment(new FragmentoDirecciones(), getString(R.string.titulo_tab_direcciones));
    adapter.addFragment(new FragmentoTarjetas(), getString(R.string.titulo_tab_tarjetas));
    viewPager.setAdapter(adapter);
    }

    Con este código, lo que hace es que cuando abro Mi Cuenta se abre *MiActivity* directamente y lo que me gustaria es que se abriera igual que los fragment.

    ¿Seria posible?.

    • No te entiendo compañero. Intentas abrir una activity junto a los fragmentos :D, ¿como es eso?

      • Rafelcf

        Si, exacto, un activity en un nuevo tab.

        • Hay alguna aplicación que haga lo que tu deseas, ¿puedes mostrarme algo mas visual compañero?

          • Rafelcf

            No no lo he provado en ninguna, salvo en tu tutorial de añadir pestañas: http://www.hermosaprogramacion.com/2015/06/tablayout-como-anadir-pestanas-en-android/ y no loo consegui tampoco, lo que me refiero es a esto:

            host.addTab(host.newTabSpec(“tab_1”).setIndicator(“J. Actual”).setContent(new Intent(this, CadeteActual.class)));

            espero que asi lo comprendas, puesto que si convierto ese listactivity a list fragment no se por que no funciona.

            Mi correo Rafelcf@gmail.com

            Gracias por tu interes.

          • Por que estás usando el host?

          • Rafelcf

            No, aqui no lo uso, es para que veas lo que quiero hacer, ya que no consigo convertir en Listfragment un ListActivity, Asi seguro que lo comprendes.

  • Ivanna

    Hola james increíbles tutoriales gracias me son de mucha ayuda, seguí todos muy buenos , ojala hagas uno con gcm php y mysql, estoy esperando ese :) ,gracias por todo

    • Gracias Ivanna. El de GCM ya está próximo, pero no que tan próximo :D, toca esperar compañera.

  • mauricio

    seguire este tutorial al pie de la letra… como peticion de algun otro tutorial, podria traer todos los datos desde una Base de datos externa mysql …. es por eso que este tutorial lo evaluo en la escala de 1 a 10 con un 9.99 xD! … gracias por compartir conocimiento…vere si logro modificarlo y conectarlo a una base de datos mysql … gracias :)

    • Gracias por tu comentario Mauricio :D , espero sea de utilidad para todos los lectores. Recomiendalo para que otros puedan verlo. Saludos!

  • Jonathan Torres

    hola james, tengo un error en appBar.removeView(pestanas); al cambiar la orientacion, que podria ser??

    • Jonathan Torres

      el error decia null, al hacer esto:
      if (appBar != null) {
      appBar.removeView(pestanas);
      }
      el error desaparecio, pero alcambiar la orientacion me regresa alinicio

      • Mm claro, el cambio de orientación afecta la insntancia de la appBar. Buena solución jonathan.

        • Jonathan Torres

          gracias y están buenos los tutoriales.

        • Juan Manuel Anton

          amigo donde se encuentra la solucion de al momento de rotar que no me salga el error que me llega null

      • Rafelcf

        Otra solución que regresa al inicio osea, pestaña PERFIL, seria, en el FragmentoCuenta, quitar la linea:

        appBar.removeView(pestanas);

      • Gon Her

        Muchas gracias por dejarnos la solución. Se agradece y lo implementé.