TabLayout: ¿Cómo Añadir Pestañas En Android?

Si deseas dividir tu contenido en subcategorías, usar un Navigation Drawer no te será suficiente. Para ello debes usar pestañas o tabs en tu aplicación Android.

Esta vez la librería de diseño no se ha olvidado de este componente tan vital para la navegación. Nos ha entregado a los Android Developers el TabLayout.

Ya sé que antes habíamos visto como añadir pestañas en la action bar, pero recuerda que el nuevo concepto de App Bar cambió todo.

El TabLayout es un componente que facilita el cambio entre un contenido y otro. Ahora va por separado y no se liga a la Action Bar directamente.

Nos provee una forma automatizada y simplificada para asociarse a un View Pager, librándonos de todo el trabajo de coordinación entre los Swipe Views y las pestañas.

Veamos como añadir un Tab Layout y de qué forma integrarlo en un ejemplo completo.

Descargar Proyecto Final En Android Studio

 

A continuación, sigue las instrucciones para desbloquear el link de descarga del proyecto final:

Añadir Tabs Con El TabLayout En Android

La lista de checkeo para Material Design nos indica de fondo que las pestañas ahora hacen parte de la superficie de la App Bar. Adicionalmente el estilo elimina las barras verticales de separación que antes se les añadía.

Tabs En Android: Lista De Checkeo En Material Design

En definición Xml simplemente incluyes dentro de la etiqueta AppBarLayout el componente TabLayout:

<android.support.design.widget.AppBarLayout
    android:id="@+id/appbar"
    ...>

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        .../>

    <android.support.design.widget.TabLayout
        android:id="@+id/tabs"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</android.support.design.widget.AppBarLayout>

El código anterior es para crear tabs predefinidas debajo de la toolbar.

Material Design: Tabs Fijas

Como ves, el TabLayout actúa como un widget normal. Así que puedes obtener su instancia programáticamente con findViewById():

TabLayout tabs = (TabLayout) findViewById(R.id.tabs);

Para añadir pestañas usa el método addTab(), el cual recibe un nuevo objeto TabLayout.Tab. Puedes obtener una instancia prefabricada con newTab() y luego setear su título con setText().

...
tabs.addTab(tabs.newTab().setText("TELÉFONOS"));
tabs.addTab(tabs.newTab().setText("TABLETS"));
tabs.addTab(tabs.newTab().setText("PORTÁTILES"));

Lo contrario a tabs fijas son las tabs deslizantes o scrollables. Su uso es excelente cuando la cantidad de pestañas es grande, ya que podemos movernos con un scroll.

Material Design: Scrollable Tabs

Activa este modo con el método TabLayout.setTabMode(). Donde debes añadir como parámetro la bandera TabLayout.MODE_SCROLLABLE.

tabs.setTabMode(TabLayout.MODE_SCROLLABLE);

Por otro lado… ¿Cómo añadir iconos en las pestañas?

Tabs En Android Con Iconos

Sencillo, usa el método setIcon():

...
tabs.addTab(tabs.newTab().setIcon(R.drawable.ic_teléfono));
tabs.addTab(tabs.newTab().setIcon(R.drawable.ic_tablet));
tabs.addTab(tabs.newTab().setIcon(R.drawable.ic_portátil));

A groso modo ese serían las herramientas básicas para el manejo de pestañas, sin embargo ten en cuenta los siguientes puntos:

  • Procesa la selección de tabs con la escucha OnTabSelectedListener. Solo añádela con setOnTabSelectedListener() y sobrescribe los controladores.
    tabs.setOnTabSelectedListener(
            new TabLayout.OnTabSelectedListener() {
                @Override
                public void onTabSelected(TabLayout.Tab tab) {
                    // ...
                }
    
                @Override
                public void onTabUnselected(TabLayout.Tab tab) {
                    // ...
                }
    
                @Override
                public void onTabReselected(TabLayout.Tab tab) {
                    // ...
                }
            }
    );
  • Si deseas que un ViewPager escuche el cambio de pestañas usa TabLayout.TabLayoutOnPageChangeListener.
    ViewPager viewPager = ...;
    TabLayout tabs = ...;
    viewPager.addOnPageChangeListener(new TabLayoutOnPageChangeListener(tabs));
  • Puedes poblar las pestañas en conjunto de las secciones de un ViewPager con setTabsFromPagerAdapter().
  • Es posible ahorrarse la coordinación entre los eventos y la población de pestañas con el método setupWithViewPager(). Esto lo veremos en el siguiente ejemplo.

Crear Proyecto En Android Studio

Paso #1. Inicia Android Studio y crea un nuevo proyecto en File > New > New Project… con el nombre de “Tricky Market“. Como viste en el video, esta aplicación es una tienda online de tecnología.

Selecciona una actividad en blanco Blank Activity y confirma.

Paso #2. Dirígete al archivo build.gradle y copia las dependencias del siguiente código:

build.gradle

dependencies {
    ...
    compile 'com.android.support:appcompat-v7:22.2.0'
    compile 'com.android.support:design:22.2.0'
    compile 'com.github.bumptech.glide:glide:3.6.0'
    compile 'com.android.support:cardview-v7:22.2.0'
    compile 'in.srain.cube:grid-view-with-header-footer:1.0.12'
}

Es necesario añadir la librería GridView With Header And Footer (u otra que te guste), ya que pondremos un ítem de cabecera dentro de la grilla.

GridView Con Header

Paso #3. Abre el archivo /res/values/strings.xml e inserta las siguientes cadenas:

strings.xml

<resources>
    <string name="app_name">Trick Market</string>

    <string name="title_section1">Teléfonos</string>
    <string name="title_section2">Tablets</string>
    <string name="title_section3">Portátiles</string>

    <string name="action_settings">Settings</string>
    <string name="action_shop">Carrito De Compras</string>
</resources>

Paso #4. Define los colores del diseño con Material Design en /res/values/colors.xml de la siguiente forma:

colors.xml

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

    <color name="primaryColor">#8BC34A</color>
    <color name="primaryDarkColor">#689F38</color>
    <color name="accentColor">#FAD317</color>
</resources>

Paso #5. Modifica el estilo de la aplicación desde el archivo /res/values/styles.xml con los colores que acabas de definir:

styles.xml

<resources>

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

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

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

</resources>

Paso #6. Crea las dimensiones de abajo en tu archivo /res/values/dimens.xml.

dimens.xml

<resources>
    <!-- Default screen margins, per the Android Design guidelines. -->
    <dimen name="activity_horizontal_margin">16dp</dimen>
    <dimen name="activity_vertical_margin">16dp</dimen>

    <dimen name="size_fab">56dp</dimen>
    <dimen name="fab_margin">16dp</dimen>

    <dimen name="badge_text_size">10sp</dimen>

    <dimen name="card_margin">16dp</dimen>

    <dimen name="grid_item_text_size">11sp</dimen>
    <dimen name="grid_view_padding">5dp</dimen>
    <dimen name="grid_item_image_size">90dp</dimen>
</resources>

Paso #7. A continuación descarga los drawables e imágenes para la aplicación y cópialas en tu directorio /res.

Aunque en el anterior archivo te entrego los iconos, puedes descargar gran variedad desde la página Material Design Icons.

Crear GridView Con Header View

Paso #8. Crea una nueva clase java llamada Product.java. Esta representará el POJO para poblar el grid view con productos de tecnología, como tablets, smartphones y portátiles.

Solo tendremos en cuenta el nombre, la descripción, el precio, el rating o valoración y la imagen asociada al producto.

/**
 * POJO de un producto
 */
public class Product {
    /**
     * Nombre del producto
     */
    private String nombre;
    /**
     * Especificaciones del producto
     */
    private String descripcion;
    /**
     * Precio del producto
     */
    private String precio;
    /**
     * Valoraciones del producto
     */
    private float rating;
    /**
     * Identificador de la imagen para miniatura
     */
    private int idThumbnail;

    public Product(String nombre, String descripcion, String precio, float rating, int idThumbnail) {
        this.nombre = nombre;
        this.descripcion = descripcion;
        this.precio = precio;
        this.rating = rating;
        this.idThumbnail = idThumbnail;
    }

    public Product() {
    }

    public String getNombre() {
        return nombre;
    }

    public String getDescripcion() {
        return descripcion;
    }

    public String getPrecio() {
        return precio;
    }

    public float getRating() {
        return rating;
    }

    public int getIdThumbnail() {
        return idThumbnail;
    }
}

Paso #9. Ahora añade una clase llamada Products.java. Su propósito es representar el modelo u origen de datos de todos los productos que tendrá la aplicación.

/**
 * Datos de prueba para las pestañas
 */
public class Products {

    private static Product[] telefonos = {
            new Product(
                    "Hisense HS-U98",
                    " 4Gb ROM, 1GB RAM, Camara 8Mpx.",
                    "$167 USD",
                    3.1f,
                    R.drawable.smartphone_hisense_hs_u98),
            new Product(
                    "DarkFull",
                    " 16Gb ROM, 2GB RAM, Camara 13Mpx.",
                    "$295 USD",
                    4.0f,
                    R.drawable.smartphone_darkfull),
            new Product(
                    "Moto G",
                    " 4Gb Rom, 1GB RAM, Camara 13Mpx.",
                    "$189.9 USD",
                    4.6f,
                    R.drawable.smartphone_motog),
            new Product(
                    "BQ Aquaris 5 HD",
                    " 16Gb ROM, 1GB RAM, Camara 8Mpx.",
                    "$199.9 USD",
                    3.0f,
                    R.drawable.smartphone_bq_aquaris_5),
            new Product(
                    "Aquaris 5.7",
                    " 16Gb ROM, 2GB RAM, Camara 13Mpx.",
                    "$259.9 USD",
                    4.6f,
                    R.drawable.smartphone_aquaris_57),

            new Product(
                    "Wiko Rainbow",
                    " 4Gb ROM, 1GB RAM, Camara 8Mpx.",
                    "$169 USD",
                    4.1f,
                    R.drawable.smartphone_wiko_rainbow),


            new Product(
                    "Hisense HS-U980",
                    " 8Gb ROM, 1GB RAM, Camara 8Mpx.",
                    "$203.9 USD",
                    2.8f,
                    R.drawable.smartphone_hisense_hs_u980)

    };

    private static Product[] tablets = {
            new Product(
                    "Apple iPad Air 2",
                    "iOS 8.1",
                    "$448 USD",
                    5.0f,
                    R.drawable.tablet_apple_ipad_air_2),
            new Product(
                    "Samsung Galaxy Tab S 8.4",
                    "Android 4.4 Kitkat",
                    "$431 USD",
                    4.0f,
                    R.drawable.tablet_samsung_galaxy_tab_s_84),
            new Product(
                    "Lenovo ThinkPad 8",
                    "Windows 8.1",
                    "$390 USD",
                    4.6f,
                    R.drawable.tablet_lenovo_thikpad_8),
            new Product(
                    "Samsung Galaxy Pro 8.4",
                    "Android",
                    "$299 USD",
                    3.0f,
                    R.drawable.tablet_galaxy_tab_pro_84),
            new Product(
                    "Amazon Kindle Fire HDX 8.9",
                    "Fire OS",
                    "$379 USD",
                    3f,
                    R.drawable.tablet_amazon_kindle_fire_hdx),

            new Product(
                    "Nvidia Shield Tablet",
                    "Android 4.4 Kitkat",
                    "$375 USD",
                    4.8f,
                    R.drawable.tablet_nvidia_shield),


            new Product(
                    "ASUS Transformer Pad Infinity TF700",
                    "Android 4.2 Jelly Bean",
                    "$509 USD",
                    4f,
                    R.drawable.tablet_asus_transformer_pad_infinity_tf700)
    };

    private static Product[] portatiles = {
            new Product(
                    "Dell Latitude 12",
                    "Model No: 7204",
                    "$6474 USD",
                    5.0f,
                    R.drawable.portatil_latitude_12_rugged),
            new Product(
                    "Alienware 17 R1 Flagship",
                    "Gaming",
                    "$3849 USD",
                    4.0f,
                    R.drawable.portatil_alienware_17_flagship),
            new Product(
                    "MSI GT80 Titan SLI",
                    "Gaming",
                    "$3299 USD",
                    4.6f,
                    R.drawable.portatil_msi_gt80_titan),
            new Product(
                    "ASUS ROG G751YJ-DH72X",
                    "Gaming",
                    "$2999 USD",
                    3.0f,
                    R.drawable.portatil_asus_rog_g751jy),
            new Product(
                    "Toshiba X70-AST3G26",
                    "All-Purpose",
                    "$2699 USD",
                    3f,
                    R.drawable.portatil_toshiba_x70_ast3g26),

            new Product(
                    "Sony VAIO Duo 13",
                    "2-in-1",
                    "$2699 USD",
                    4.8f,
                    R.drawable.portatil_sony_vaio_duo_13_svd1322bpxb),


            new Product(
                    "Gigabyte Aorus X3 Plus v3",
                    "Gaming",
                    "$2538 USD",
                    4f,
                    R.drawable.portatil_gigabyte_aorus_x3_plus_v3)
    };

    public static Product[] getTelefonos() {
        return telefonos;
    }

    public static Product[] getTablets() {
        return tablets;
    }

    public static Product[] getPortatiles() {
        return portatiles;
    }
}

Paso #10. Para la representación gráfica de los ítems del grid view crea un nuevo archivo en /res/layout/ llamado grid_item.xml y añade el código de abajo. La valoración del artículo se representa con una rating bar.

grid_item.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginBottom="@dimen/card_margin"
        android:layout_marginLeft="@dimen/card_margin"
        android:layout_marginRight="@dimen/card_margin"
        android:layout_marginTop="@dimen/card_margin"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/imagen"
            android:layout_width="@dimen/grid_item_image_size"
            android:layout_height="@dimen/grid_item_image_size"
            android:layout_gravity="center_horizontal"
            android:scaleType="fitCenter" />

        <TextView
            android:id="@+id/nombre"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:text="Nombre"
            android:textAppearance="?android:attr/textAppearanceSmall"
            android:textColor="@android:color/black"
            android:textSize="@dimen/grid_item_text_size" />

        <TextView
            android:id="@+id/descripcion"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:gravity="center"
            android:text="Descripción"
            android:textAppearance="?android:attr/textAppearanceSmall"
            android:textSize="@dimen/grid_item_text_size" />

        <TextView
            android:id="@+id/precio"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:text="$ Precio"
            android:textAppearance="?android:attr/textAppearanceSmall"
            android:textColor="@color/primaryColor"
            android:textSize="@dimen/grid_item_text_size" />

        <RatingBar
            android:id="@+id/rating"
            style="?android:attr/ratingBarStyleSmall"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:clickable="true"
            android:isIndicator="true"
            android:paddingTop="8dp"
            android:progressTint="#FDDB39"
            android:rating="3"
            android:secondaryProgressTint="#FDDB39" />
    </LinearLayout>
</android.support.v7.widget.CardView>

Paso #11. Crea otro layout nuevo llamado grid_header.xml y cambia las posiciones de la siguiente forma:

grid_header.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="@dimen/activity_horizontal_margin">

        <ImageView
            android:id="@+id/imagen"
            android:layout_width="@dimen/grid_item_image_size"
            android:layout_height="@dimen/grid_item_image_size"
            android:layout_alignParentLeft="true"
            android:layout_alignParentStart="true"
            android:layout_centerVertical="true"
            android:layout_marginRight="@dimen/activity_horizontal_margin" />

        <TextView
            android:id="@+id/nombre"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_toRightOf="@+id/imagen"
            android:text="Nombre"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:textColor="@android:color/black" />

        <TextView
            android:id="@+id/descripcion"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignLeft="@+id/nombre"
            android:layout_alignStart="@+id/nombre"
            android:layout_below="@+id/nombre"
            android:text="Descripción"
            android:textAppearance="?android:attr/textAppearanceSmall" />

        <TextView
            android:id="@+id/precio"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignLeft="@+id/descripcion"
            android:layout_alignStart="@+id/descripcion"
            android:layout_below="@+id/descripcion"
            android:text="$ Precio"
            android:textAppearance="?android:attr/textAppearanceSmall"
            android:textColor="?colorPrimary" />

        <RatingBar
            android:id="@+id/rating"
            style="?android:attr/ratingBarStyleSmall"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignLeft="@+id/precio"
            android:layout_below="@+id/precio"
            android:layout_gravity="center_horizontal"
            android:clickable="true"
            android:isIndicator="true"
            android:paddingTop="8dp"
            android:progressTint="#FDDB39"
            android:rating="3"
            android:secondaryProgressTint="#FDDB39" />
    </RelativeLayout>
</android.support.v7.widget.CardView>

Crear Adaptador Para El GridView

Paso #12. Lo siguiente es crear un adaptador personalizado para el grid view. Para ello añade una nueva clase llamada GridAdapter.java y pega el siguiente código.

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.RatingBar;
import android.widget.TextView;

import com.bumptech.glide.Glide;

/**
 * {@link android.widget.BaseAdapter} personalizado para el gridview
 */
public class GridAdapter extends BaseAdapter {

    private final Context mContext;
    private final Product[] items;

    public GridAdapter(Context c, Product[] items) {
        mContext = c;
        this.items = items;
    }

    @Override
    public int getCount() {
        // Decremento en 1, para no contar el header view
        return items.length - 1;
    }

    @Override
    public Product getItem(int position) {
        return items[position];
    }

    @Override
    public long getItemId(int position) {
        return 0;
    }


    @Override
    public View getView(int position, View view, ViewGroup viewGroup) {

        if (view == null) {
            LayoutInflater inflater = (LayoutInflater) mContext
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            view = inflater.inflate(R.layout.grid_item, viewGroup, false);
        }

        Product item = getItem(position);

        // Seteando Imagen
        ImageView image = (ImageView) view.findViewById(R.id.imagen);
        Glide.with(image.getContext()).load(item.getIdThumbnail()).into(image);

        // Seteando Nombre
        TextView name = (TextView) view.findViewById(R.id.nombre);
        name.setText(item.getNombre());

        // Seteando Descripción
        TextView descripcion = (TextView) view.findViewById(R.id.descripcion);
        descripcion.setText(item.getDescripcion());

        // Seteando Precio
        TextView precio = (TextView) view.findViewById(R.id.precio);
        precio.setText(item.getPrecio());

        // Seteando Rating
        RatingBar ratingBar = (RatingBar) view.findViewById(R.id.rating);
        ratingBar.setRating(item.getRating());

        return view;
    }
}

Paso #13. Añade un layout llamado fragment_main.xml en /res/layout/ y defínelo de la siguiente manera.

fragment_main.xml

<in.srain.cube.views.GridViewWithHeaderAndFooter 
xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/gridview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:horizontalSpacing="@dimen/grid_view_padding"
    android:numColumns="2"
    android:padding="@dimen/grid_view_padding"
    android:stretchMode="columnWidth"
    android:verticalSpacing="@dimen/grid_view_padding" />

GridViewWithHeaderAndFooter es la variación del grid view que viene en la librería que implementamos.

Paso #14. Ahora falta crear un fragmento con un grid view para poblar las secciones del view pager. En nuestro caso tendremos tres secciones: "TELÉFONOS", "TABLETS" y "PORTÁTILES".

Aunque podría crearse un fragmento para cada una, he decidido por usar una estructura switch que infle la grilla con los datos correspondientes a la sección. Solución viable, ya que el contenido es similar.

Crea una nueva clase con el nombre de GridFragment.java e implementala así:

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

import com.bumptech.glide.Glide;

import in.srain.cube.views.GridViewWithHeaderAndFooter;

/**
 * Un fragmento que contiene una grilla de productos
 */
public class GridFragment extends Fragment {
    /**
     * Argumento que representa el número sección al que pertenece
     */
    private static final String ARG_SECTION_NUMBER = "section_number";

    /**
     * Creación prefabricada de un {@link GridFragment}
     */
    public static GridFragment newInstance(int sectionNumber) {
        GridFragment fragment = new GridFragment();
        Bundle args = new Bundle();
        args.putInt(ARG_SECTION_NUMBER, sectionNumber);
        fragment.setArguments(args);
        return fragment;
    }

    public GridFragment() {
    }

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

        // Obtención del grid view
        GridViewWithHeaderAndFooter grid =
                (GridViewWithHeaderAndFooter) rootView.findViewById(R.id.gridview);

        // Inicializar el grid view
        setUpGridView(grid);

        return rootView;
    }

    /**
     * Infla el grid view del fragmento dependiendo de la sección
     *
     * @param grid Instancia del grid view
     */
    private void setUpGridView(GridViewWithHeaderAndFooter grid) {
        int section_number = getArguments().getInt(ARG_SECTION_NUMBER);
        switch (section_number) {
            case 1:
                grid.addHeaderView(createHeaderView(6, Products.getTelefonos()));
                grid.setAdapter(new GridAdapter(getActivity(), Products.getTelefonos()));
                break;
            case 2:
                grid.addHeaderView(createHeaderView(6, Products.getTablets()));
                grid.setAdapter(new GridAdapter(getActivity(), Products.getTablets()));
                break;
            case 3:
                grid.addHeaderView(createHeaderView(6, Products.getPortatiles()));
                grid.setAdapter(new GridAdapter(getActivity(), Products.getPortatiles()));
                break;
        }
    }

    /**
     * Crea un view de cabecera para mostrarlo en el principio del grid view.
     *
     * @param position Posición del item que sera el grid view dentro de {@code items}
     * @param items    Array de productos
     * @return Header View
     */
    private View createHeaderView(int position, Product[] items) {

        View view;

        LayoutInflater inflater = getActivity().getLayoutInflater();
        view = inflater.inflate(R.layout.grid_header, null, false);

        Product item = items[position];

        // Seteando Imagen
        ImageView image = (ImageView) view.findViewById(R.id.imagen);
        Glide.with(image.getContext()).load(item.getIdThumbnail()).into(image);

        // Seteando Nombre
        TextView name = (TextView) view.findViewById(R.id.nombre);
        name.setText(item.getNombre());

        // Seteando Descripción
        TextView descripcion = (TextView) view.findViewById(R.id.descripcion);
        descripcion.setText(item.getDescripcion());

        // Seteando Precio
        TextView precio = (TextView) view.findViewById(R.id.precio);
        precio.setText(item.getPrecio());

        // Seteando Rating
        RatingBar ratingBar = (RatingBar) view.findViewById(R.id.rating);
        ratingBar.setRating(item.getRating());

        return view;
    }
}

Importante resaltar que:

  • Se creó el método createHeaderView() para inflar un view con el layout grid_header.xml.
  • El método addHeaderView() añade el header al GridView.

Añadir Notificación Con Contador En Action Button

Paso #15. El siguiente paso es crear el menú de la Toolbar dentro de la carpeta /res/menu con el nombre de menu_main.xml.

menu_main.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=".MainActivity">
    <item
        android:id="@+id/action_settings"
        android:orderInCategory="100"
        android:title="@string/action_settings"
        app:showAsAction="never" />
    <item
        android:id="@+id/action_shop"
        android:icon="@drawable/notification_drawable"
        android:orderInCategory="1"
        android:title="@string/action_shop"
        app:showAsAction="ifRoom" />
</menu>

Paso #16. Ahora crea un nuevo archivo en /res/drawable/ llamado notification_drawable.xml. Este será usado para crear el icono del carrito de compras con una notificación circular en su parte superior.

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/ic_notification"
        android:drawable="@drawable/ic_cart"
        android:gravity="center" />

    <!-- Insignia flotante -->
    <item
        android:id="@+id/ic_badge"
        android:drawable="@drawable/ic_cart" />
</layer-list>

Paso #17. Lo siguiente es crear una nueva clase llamada BadgeDrawable.java que extienda de Drawable. Con ella dibujaremos el círculo sobre el recurso creado previamente.

Action Button Con Notificación

Este procedimiento para decorar un action button con una notificación, está basado en el artículo de Jesse Hendrickson denominado “Dynamically add flair to ActionBar MenuItems”. De verdad es muy interesante y te recomiendo que lo leas.

BadgeDrawable.java

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;

/**
 * Drawable con un contador flotante
 */
public class BadgeDrawable extends Drawable {

    private float mTextSize;
    private Paint mBadgePaint;
    private Paint mTextPaint;
    private Rect mTxtRect = new Rect();

    private String mCount = "";
    private boolean mWillDraw = false;

    public BadgeDrawable(Context context) {
        mTextSize = context.getResources().getDimension(R.dimen.badge_text_size);

        mBadgePaint = new Paint();
        mBadgePaint.setColor(Color.RED);
        mBadgePaint.setAntiAlias(true);
        mBadgePaint.setStyle(Paint.Style.FILL);

        mTextPaint = new Paint();
        mTextPaint.setColor(Color.WHITE);
        mTextPaint.setTypeface(Typeface.DEFAULT_BOLD);
        mTextPaint.setTextSize(mTextSize);
        mTextPaint.setAntiAlias(true);
        mTextPaint.setTextAlign(Paint.Align.CENTER);
    }

    @Override
    public void draw(Canvas canvas) {
        if (!mWillDraw) {
            return;
        }

        Rect bounds = getBounds();
        float width = bounds.right - bounds.left;
        float height = bounds.bottom - bounds.top;

        // Position the badge in the top-right quadrant of the icon.
        float radius = ((Math.min(width, height) / 2) - 1) / 2;
        float centerX = width - radius - 1;
        float centerY = radius + 1;

        // Draw badge circle.
        canvas.drawCircle(centerX, centerY, radius, mBadgePaint);

        // Draw badge count text inside the circle.
        mTextPaint.getTextBounds(mCount, 0, mCount.length(), mTxtRect);
        float textHeight = mTxtRect.bottom - mTxtRect.top;
        float textY = centerY + (textHeight / 2f);
        canvas.drawText(mCount, centerX, textY, mTextPaint);
    }

    /*
    Sets the count (i.e notifications) to display.
     */
    public void setCount(int count) {
        mCount = Integer.toString(count);

        // Only draw a badge if there are notifications.
        mWillDraw = count > 0;
        invalidateSelf();
    }

    @Override
    public void setAlpha(int alpha) {
        // do nothing
    }

    @Override
    public void setColorFilter(ColorFilter cf) {
        // do nothing
    }

    @Override
    public int getOpacity() {
        return PixelFormat.UNKNOWN;
    }
}

Paso #18. A continuación crea una clase llamada Utils.java. La idea es añadirle un método estático llamado setBadgeCount() para asignar el valor del contador de la notificación.

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;

/**
 * Clase de utilidades
 */
public class Utils {

    public static void setBadgeCount(Context context, LayerDrawable icon, int count) {

        BadgeDrawable badge;

        // Reusar drawable
        Drawable reuse = icon.findDrawableByLayerId(R.id.ic_badge);
        if (reuse != null && reuse instanceof BadgeDrawable) {
            badge = (BadgeDrawable) reuse;
        } else {
            badge = new BadgeDrawable(context);
        }

        badge.setCount(count);
        icon.mutate();
        icon.setDrawableByLayerId(R.id.ic_badge, badge);
    }
}

Paso #19. Ve a /res/layout/ y modifica el layout de la actividad principal. Es importante usar un CoordinatorLayout para el botón flotante que usaremos en la parte inferior derecha. También por si quieres esconder la Toolbar y dejar fijas las pestañas al realizar scrolling.

activity_main.xml

<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/coordinator"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v4.view.ViewPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

    <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.TabLayout
            android:id="@+id/tabs"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </android.support.design.widget.AppBarLayout>


    <!-- FAB -->
    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="@dimen/size_fab"
        android:layout_height="@dimen/size_fab"
        android:layout_margin="@dimen/fab_margin"
        android:onClick="onFabClick"
        android:src="@drawable/ic_search"
        app:borderWidth="0dp"
        app:elevation="@dimen/fab_elevation"
        app:layout_anchor="@id/pager"
        app:layout_anchorGravity="bottom|right" />
</android.support.design.widget.CoordinatorLayout>

Paso #20. Ahora abre el archivo MainActivity.java  y modifícalo con el código de abajo. Ten en cuenta que debes crear un FragmentPagerAdapter para el ViewPager.

Luego relaciona el TabLayout con el view pager a través del método setupWithViewPager() para inflar las tres pestañas basada en secciones.

import android.graphics.drawable.LayerDrawable;
import android.os.Bundle;
import android.support.design.widget.Snackbar;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;

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

public class MainActivity extends AppCompatActivity {
    ViewPager mViewPager;

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

        setToolbar(); // Añadir la toolbar

        // Setear adaptador al viewpager.
        mViewPager = (ViewPager) findViewById(R.id.pager);
        setupViewPager(mViewPager);

        // Preparar las pestañas
        TabLayout tabs = (TabLayout) findViewById(R.id.tabs);
        tabs.setupWithViewPager(mViewPager);
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        MenuItem item = menu.findItem(R.id.action_shop);

        // Obtener drawable del item
        LayerDrawable icon = (LayerDrawable) item.getIcon();

        // Actualizar el contador
        Utils.setBadgeCount(this, icon, 3);

        return true;
    }


    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();

        if (id == R.id.action_shop) {
            showSnackBar("Carrito de compras");
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    /**
     * Muestra una {@link Snackbar} prefabricada
     *
     * @param msg Mensaje a proyectar
     */
    private void showSnackBar(String msg) {
        Snackbar.make(findViewById(R.id.fab), msg, Snackbar.LENGTH_LONG).show();
    }

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

    }

    /**
     * Crea una instancia del view pager con los datos
     * predeterminados
     *
     * @param viewPager Nueva instancia
     */
    private void setupViewPager(ViewPager viewPager) {
        SectionsPagerAdapter adapter = new SectionsPagerAdapter(getSupportFragmentManager());
        adapter.addFragment(GridFragment.newInstance(1), getString(R.string.title_section1));
        adapter.addFragment(GridFragment.newInstance(2), getString(R.string.title_section2));
        adapter.addFragment(GridFragment.newInstance(3), getString(R.string.title_section3));
        viewPager.setAdapter(adapter);
    }

    /**
     * Método onClick() del FAB
     *
     * @param v View presionado
     */
    public void onFabClick(View v) {
        showSnackBar("Buscar producto");
    }

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

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

        @Override
        public Fragment getItem(int position) {
            return mFragments.get(position);
        }

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

        public void addFragment(Fragment fragment, String title) {
            mFragments.add(fragment);
            mFragmentTitles.add(title);
        }

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

}

Paso #21. Por último corre la aplicación en Android Studio para ver nuestra tienda online de tecnología Trick Market.

Tienda Online En Android Con Tabs

Conclusión

El TabLayout permite cambiar entre views de forma fácil y amigable.

Hemos visto cómo crear un Grid View e integrar distintos productos de una tienda online en cada sección de un View Pager.

Aunque no es un ejemplo completo, muestra como las tabs pueden optimizar la transición entre cada segmento de contenido.

Lo ideal sería realizar peticiones Http desde Android hacia un servidor externo, que almacene la información de datos reales.

¿Cuál es el siguiente paso?

Aprender a usar transiciones entre actividades y generar animaciones en las interacciones de distintos views.

 

Ícono de la aplicación