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

  • Rafael

    Como hacer para que cuando haga click en una pestaña me salgan otras 2 bajo?

  • Oscar

    buenas gente, estoy haciendo un trabajo de fin de grado que consiste en un app que aglutine todos los sensores disponibles en el smartphone (sonido, luz, presion, acelerometro… etc) y uno de los activities utilice tabs para mostrar todos con scroll horizontal para que todos aparezcan en la misma ventana, pero dependiendo el tab, muestre un layout tab distinto.

    Hasta ahi, todo correcto, pero nose donde colocar/nombrar/referenciar el código java para cada uno de los tabs, nose si me he explicado bien… pero necesito ayuda, gracias!!

  • Oscar

    buenas gente, estoy haciendo un trabajo de fin de grado que consiste en un app que aglutine todos los sensores disponibles en el smartphone (sonido, luz, presion, acelerometro… etc) y uno de los activities utilice tabs para mostrar todos con scroll horizontal para que todos aparezcan en la misma ventana, pero dependiendo el tab, muestre un layout tab distinto.

    Hasta ahi, todo correcto, pero nose donde colocar/nombrar/referenciar el código java para cada uno de los tabs, nose si me he explicado bien… pero necesito ayuda, gracias!!

  • Luis Troya

    Excelente tutorial, me ayudo a implementar los tabs con el navigation drawer de una manera muy sencilla

  • ferje

    hola quisiera saber como instalar la libreria GridView With Header And Footer ??? ayuda por fabor

  • Edgar Aguilar

    BUENAS NOCHES QUISIERA SABER O SI ALGUIEN ME PUEDE AYUDAR, ME GUSTARÍA SABER COMO ASIGNARLE FUNCIONALIDAD A LAS IMÁGENES DENTRO DEL TAB,

    OSEA QUE CUANDO SELECCIONE UNA OPCIÓN ME MANDE A UN VÍDEO O A OTRA ACTIVIDAD.

    AGRADEZCO SU ATENCIÓN Y SI ME PUEDEN AYUDAR MUCHO MEJOR GRACIAS.

    • Luis Troya

      Agregale un onClick al ImageView

  • Marlon Arteaga Morales

    Una pregunta desde que versión de android puedo implementar esto de TabLayout, por favor si alguien respondiera mi duda.
    Cambio y Fuera

    • Luis Troya

      Desde la version 2.3 en adelante

  • User X

    James, espero me puedas ayudar en el paso #5.

    Este es el código que das para modificar:

    @color/primaryColor
    @color/primaryDarkColor
    @color/accentColor

    @color/window_background

    Y este es el código que tengo por defecto:


    @color/colorPrimary
    @color/colorPrimaryDark
    @color/colorAccent

    false
    true

    Ya intente reemplazándolo o modificando directamente pero cuando veo la vista me sale el sig. error:

    Alguna sugerencia de cual es el orden que debe tener, gracias :D

  • Daniel

    hola buenas tarde, tengo un problema con los tablayout : tengo 3 tabs, en cada uno hago peticiones post usando la libreria volley y cargo la informacion a un listview. Todo va bien en las peticiones pero el detalle está en que, por ejemplo, si estoy en el tab 1 y cambio al tab 3, en este ultimo se vuelve ha realizar la peticion del tab 1 y cuando vuelvo al tab 1, los datos en el listivew se han duplicado: practicamente es como si el tab 1 se hubiese reiniciado sin que este en él. Facilmente puedo borrar la hacer listview.clear() para que no se duplique pero lo que no quiero esque el fragment del tab se reinicie. Esto ocurre con los tres tabs, cambiando entre uno y uno los otros se reinician. SE QUE ES UN PROBLEMA CON LOS TABS Y MAS NO CON LAS PETICIONES. espero puedas ayudarme

    ESTO COLOCO EN LA ACTIVIDAD DONDE SE ALOJAN LOS TABS

    private void setupTabIcons() {
    tabLayout.getTabAt(0).setIcon(tabIcons[0]);
    tabLayout.getTabAt(1).setIcon(tabIcons[1]);
    tabLayout.getTabAt(2).setIcon(tabIcons[2]);
    }

    private void setupViewPager(ViewPager viewPager) {
    ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager());
    adapter.addFragment(new CameraFragment(), “CameraFragment”);
    adapter.addFragment(new RecentlyFragment(), “RecentlyFragment”);
    adapter.addFragment(new AllFragment(), “AllFragment”);
    viewPager.setAdapter(adapter);
    }

    class ViewPagerAdapter extends FragmentPagerAdapter {
    private final List mFragmentList = new ArrayList();
    private final List mFragmentTitleList = new ArrayList();

    public ViewPagerAdapter(FragmentManager manager) {
    super(manager);
    }

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

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

    public void addFragment(Fragment fragment, String title) {
    mFragmentList.add(fragment);
    mFragmentTitleList.add(title);
    }

    @Override
    public CharSequence getPageTitle(int position) {
    //return mFragmentTitleList.get(position);
    return null;
    }
    }

    • Luis Troya

      Tienes que limpiar la data en adapter y llamar el metodo notifyDataSetChanged()

  • Alberto Cruz

    hola buenas tardes oyes solo para preguntarte si quisiera aplicar esta misma aplicacion para mi negocio como puedo lograr que se enlace con una base de datos para que me saque toda la informacion de la base de datos incluyendo fotos ,,,,,,,,, ?

    • Hola Alberto, no comprendo tu pregunta. Supongo que la respuesta es que uses una base de datos SQLite para obtener los datos?

      Puedes explicarte mejor?

  • Rodrigo Cortes (Blue-Sens)

    BUENAS NOCHES QUISIERA SABER O SI ALGUIEN ME PUEDE AYUDAR, ME GUSTARÍA SABER COMO ASIGNARLE FUNCIONALIDAD A LAS IMÁGENES DENTRO DEL TAB,

    OSEA QUE CUANDO SELECCIONE UNA OPCIÓN ME MANDE A UN VÍDEO O A OTRA ACTIVIDAD.

    AGRADEZCO SU ATENCIÓN Y SI ME PUEDEN AYUDAR MUCHO MEJOR GRACIAS.

    • Marlon Arteaga Morales

      Claro sólo le tienes que dar un
      Item item =(item)findViewById(R.id.idItem);
      item.setOnClickListener
      y Listo luego tienes que hacer el Intent del item.
      startActivity(new Intent(ActivityMain.this,SegundoMain.class);)
      y va a ir al SegundoMain, osea el activity que quieras sería.

      • Edgar Aguilar

        Hola yo estoy haciendo lo mismo y no me sale

        • Marlon Arteaga Morales

          Te sale error de sintaxis o al compilar ?. Quizás estás casteando algo diferente para darle el findViewById();

  • nano contrera

    buenas quisiera saber como implementar con un json, osea tenia uno pero utilizaba el tabactivity que esta deprecated , uso GSON en mi listview que se consume de un JSON desde ya muchas gracias ah por cierto te puedo escribir a tu mail quisiera saber si me puedes orientar en ciertas cosas he ir retribuyendote por los conocimientos adquiridos desde ya muchas gracias por los tutoriales

  • Christian Sari

    Una consulta, buenos dias James, una pregunta para guiarme, quiero usar este tutorial para hacer una app con tabs, pero cada tab envia a un contenido diferente, es decir leera xmls o json, como puedo hacerlo asi, tengo comprendido que debo usar fragment’s diferentes? y en cada fragment el lector o parser?.

    Espero su respuesta. Gracias.

  • Raverito Stone

    Buen tutorial, tengo una pregunta como hago para que por defecto aparasca el tab de X posicion y no el primero.

  • Henry Sabogal

    Hola, gracias por el tutorial, me ha servido bastante. Quería preguntar como se hace para mostrar los íconos en el tab cuando cuando se asocia con el ViewPager. Ya utilice la función setIcon para el TabLayout pero no se muestran. De nuevo gracias por compartir

  • dago23

    hola, excelente tutorial.muy bueno la verdad.
    necesitaria saber si me
    podrias colaborar en la utilizacion del carrito de compras, para que
    este reciba eventos de agregar articulos y mostrando la notificacion por
    cada articulo agregado…seria de gran ayuda cualquier
    sugerencia…Gracias!!

  • Mateo Prieto

    Quiero saber como usar el carrito de compras quiero agregar uno y no entindo e.e
    osea no puedo agregar ob ejetos a el y luego verlos

  • Xuelo

    Necesito ayuda, no consigo poblar cada pestaña

  • Xuelo

    Grandisimo tutorial!
    No consigo descargar el código completo he retwiteado y dado a like y nada….

    • Xuelo

      solucionado

  • Rodrigo Dámazo

    Hola James.. eh seguido todos tus tutoriales desde hace un tiempo… Siento que tus aportes son muy utiles por el echo de que usas metodos y/o herramientas actualizadas, aunado a la explcación detalla que ofreces.

    Use este tutorial para colocar tabs en mi proyecto, con la diferencia que uso 3 fragments, correspondientes a cada pestaña. El problema esta en que los fragments no se ven completos (no se colocan debajo del tab layout) uso los mismos widgets que colocas en el activity_main.xml a excepcion del FloatingActionButton… a que crees que se deba?

    • Hola compañero, gracias por tu apreciación :)

      De seguro es el layout y las restricciones que tienes. Muestrame el contenido.

      • Rodrigo Dámazo

        Hola james, gracias pero afortunadamente ya resolvi el problema, solo agrupe todo en un linearlayout y los fragments ya me los coloco debajo del tabLayout..gracias seguire al tanto de tus aportes…

        • Manuel Alonso Talledo Rondoy

          Hola rodrigo puedes ayudarme a poner un fragment por cada pestaña, necesito poner 2 pestañas con su correspondiente fragment xml. Si puedes poner mediante un ejemplo.

          • Rodrigo Dámazo

            Hola manuel..usa las dos ultimos fragmentos de codigo que muestra james,evita los widgets que no usaras y cambia este fragmento de codigo:
            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);
            }

            **ese es codigo de james, lo que cambiaras es el fragmento que dice //setear adaptador….
            por algo similar a esto:

            mViewPager=(ViewPager)findViewById(R.id.pager);
            PagerAdapter adapter = new PagerAdapter(getSupportFragmentManager());
            adapter.addFragment(new TuFragmento1(),””);
            adapter.addFragment(new Tufragmento2(),””);
            mViewPager.setAdapter(adapter);

            **tus fragments deben tener un constructor vacio. No se si me explique =)

  • Damian Condrac

    Tengo una pregunta…..si pongo un boton en algun fragment de los creados y quiero que al hacerle click sea reemplazado por otro, como puedo hacerlo ?

    • Hola Damian. Prueba usando FragmentTransaction.replace() para reemplazar el contenido del fragmento:

      final FragmentTransaction ft = getFragmentManager().beginTransaction();
      ft.replace(R.id.contenedor, new FragmentoAlterno(), “Etiqueta”);
      ft.commit();

      • Damian Condrac

        Gracias por contestar. Mi problema es que pongo de contenedor el wiewpager y no muestra el layout al hacer el replace , eso es incorrecto?. Mi axml contiene solo un tablayout y viewpager…me estaria faltando algo?

  • Nicolas

    Hola James ,, te hago una pregunta , como puedo hacer para que el TabLayout inicie por default en la pestaña que yo quiera , por ejempli de tres pestañas , que inicie en la del medio ?Gracias por los tutos

    • Hola Nicolas. A vuelo de pájaro se me ocurre que uses el método TabLayout.getTabAt() para obtener la pestaña en la posición que desees y luego con el resultado obtenido usas Tab.select() para seleccionarla. No se si sea de ayuda mi respuesta :v

      • CarlosCarEd

        Hola Jemes¡ Primero que nada Muchas gracias por los tutoriales me han sido de mucha utilidad :D

        y bueno tengo una pregunta. Intente hacer 3 fragmentos ya que en una pestaña quiero un RecyclerView y en las otras 2 un GridView. El fragment del RecyclerView lo intente hacer de la siguiente forma:

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

        reciclador = (RecyclerView) rootView.findViewById(R.id.reciclador);

        layoutManager = new LinearLayoutManager(this);
        reciclador.setLayoutManager(layoutManager);

        EjercicioAdapter adaptador = new EjercicioAdapter();
        reciclador.setAdapter(adaptador);

        return rootView;

        }y me sale el siguiente error:Error:(32, 49) error: incompatible types: RecyclerFragment cannot be converted to Context

        ¿Que estoy haciendo mal? Soy noob :c

        • Hola. Creería que es por usas la referencia this cuando creas el nuevo Linear layout. Allí usa getActivity() en reemplazo. Prueba y me dices.

  • Kevin Bolaños

    Excelente tutorial!! Pero como hacer para que funcione el scroll de la tooblar … Ya puse el

    app:layout_scrollFlags=”scroll|enterAlways”

    tanto en el toolbar como el tablayout .. Pero no hace el efecto.. Alguna solución o solo se podra con un RecyclerView? De antemano Gracias !

    • Al parecer solo el RecyclerView y el NesteScrollView pueden hacerlo. Hace poco lo probé con un ListView y no funcionaba.

  • Chris Santiago

    Bueno eh descargado el ejemplo y ejecutarlo en el movil pero al girar el movil la aplicacion se detiene.

  • Rafelcf

    Como puedo crear un tab y abrir dentro un listfragment? Tercera pestaña. Gracias

    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));
    new ListFragment(CadeteActual.class.newInstance(3), getString(R.string.title_section3));
    adapter.addFragment(GridFragment.newInstance(4), getString(R.string.title_section4));

    }

    • Hola compañero.

      Estoy escribiendo un artículo para usar tabs con diferentes fragmentos. Espero te sea de utilidad.

      • Rafelcf

        Muchas gracias

      • Rafelcf

        Perdona mi impaciencia, pero, para cuando?

  • Sergio Del Villar

    Buen día!
    Si quisiera tener varios idiomas, cómo podría hacerlo dentro de products.java?
    Cómo podría cambiar

    new Product(
    “Hisense HS-U98″,
    ” 4Gb ROM, 1GB RAM, Camara 8Mpx.”,
    “$167 USD”,
    a variables -declaradas en string.xml?-

    Me ayudaría un montón al desarrollo! :D

    • Hola Sergio.

      Si claro, cargar los datos desde strings.xml sería buena opción. Allí puedes crear todo tipo de idioma. Sin embargo sería estático.

      Una solución rápida que se me ocurre en este momento (obvio deben haber otras) es declarar tu clase Product como anidada estática dentro de la actividad o fragmento donde deseas usarla.

      Luego simplemente usas la clase Resources para obtener una instancia programática de los recursos. Con eso puedes acceder al método getString(R.string.ID_del_recurso) y así rellenas el constructor de cada elemento.

      Puedes ver mas sobre recursos aquí: http://www.hermosaprogramacion.com/2015/08/uso-de-recursos-en-android/

      Si tuvieras que leer desde un servidor externo, entonces el problema debes resolverlo desde allá y no localmente en los recursos.

  • Amanda Lopez

    Hola.
    Muy buen tuttorial.
    Me gustaria saber como puedo unir esto con el drawer layout.
    En mi caso tengo un drawer layout que me lleva a determinados fragments.
    En uno de ellos quiero poner tablayout para mostrar los productos como tu pero no se como unirlo.
    Alguien sabe?

    • Hola Amanda.

      ¿Que has intentado hasta el momento?

      Lo que se me ocurre es que en el fragmento que tiene pestañas, se le añada un tablayout a su layout.

      Sin embargo parece que este tema afecta a muchas personas. Voy a terminar un artículo del uso del archivo strings.xml y luego empiezo un tutorial para hacer eso que dices.

      Solo es cuestión de paciencias :D

      Saludos!

      • Amanda Lopez

        Gracias. Lo esperaré.

  • Aniel Cisneros

    Hola amigo James…

    Por aquí ando molestando nuevamente. Seguí al pie de la letra este tutorial (con la diferencia que en lugar de Grids lo llene de datos obtenidos del POJO pero usando un ListView.

    Mi problema llega al intentar crear un evento OnClick para cada ítem del ListView, ya que como sabemos, el grid (ListView) se llama gridview y se encuentra en el layout fragment_secc, pero la clase que lo controla pueden ser el GridFragment.java aún así usas el GridAdapter para inflar cada ítem (grid_item.xml) y además el MainActivity.java que controla todos los elementos de la actividad… En si no sé donde agregar el evento OnClickListener ni a que objeto se lo tengo que programar…

    ¿Podrías orientarme?

    • Hola Aniel.

      El gridview hace parte del fragmento. Puedes incluir la escucha en el método setupGridView() a lo ultimo del switch. Recuerda que este método es el encargado de preparar todas las caracteristicas de la grilla.

      • Aniel Cisneros

        Hola buen día, he insertado el código en el lugar donde me indicas, pero me marca error, te adjunto esta captura para que cheques que me falta declarar o hacer…

        Saludos!

        • Allí mismo te está diciendo que debes quitar la referncia AdapterView y dejar solo OnItemClickListener.

        • Aniel Cisneros

          Ya está solucionado!!!

          ¡Gracias!

          Ahora lo que estoy buscando es configurar un popupMenu horizontal (con iconos y/o colores configurables) quizá creado a través de un Layout… como un “Toast” personalizado, con botones. :) ¿Quizá un Diálogo personalizado?

          ¿Que me recomiendas?

          • Mmm…¿tienes una imagen de algún aplicación que haga eso que deseas?

  • Robert Stark

    Hola James, primero que todo te quería felicitar por la gran labor que realizas al compartir tus conocimientos y experiencias con todos; mi pregunta es la siguiente y es que he realizado una aplicación basada en la libreria GridView with Header and footer la cual utilizas en este tutorial y me gustaría saber como puedo lidiar con los eventos al clickiar una imagen de un producto de este gridView (la lista de productos así como tabs están dentro de un fragmento en mi app), si me pudieras ayudar te lo agradecería un montón ya que he intentado algunas alternativas y no he podido.

    • Hola Robert.

      Lo primero que se me ocurre es usar una escucha OnItemClickListener sobre el grid. Algo como:

      grid.setOnItemClickListener(
      new AdapterView.OnItemClickListener() {
      @Override
      public void onItemClick(AdapterView parent, View view, int position, long id) {
      Toast.makeText(getActivity(), “Item en posicion:”+position, Toast.LENGTH_SHORT).show();
      }
      }
      );

      ¿Ya has probado eso?

      • Robert Stark

        Hola James si a la verdad lo había probado pero no el el lugar correcto así que al ponerlo sobre el grid todo estuvo bien!, de verdad te agradezco mucho por orientarme, aunque ahora al realizar el click sobre los items al ver los mensajes toast el header no se contabiliza, pero lo demás si. y mi otra pregunta es como podría iniciar un fragmento en este punto al clickiar en los items? desde ya te estoy enormemente agradecido

        • Rafelcf

          Podiais decir el lugar correcto?

          grid y getActivity se me queda en rojo, alguna idea?

          • Robert Stark

            hola método onCreateView del grid podría servirte

        • Puedes hacerlo con el administrado de fragmentos más una transacción de añadidura. Algo como:

          FragmentManager fm = getFragmentManager();
          FragmentTransaction ft = fm.beginTransaction();

          Fragment fragmentoNuevo = new FragmentoNuevo();
          // Declara argumentos si es que hay
          ft.add(R.id.el_contenedor_donde_se_pondra, fragmentoNuevo);
          ft.commit();

  • Richard Daniel

    Un afectuoso saludo James, sí nuevamente soy yo, es una pregunta apegada a este tema, mi duda es la siguiente:
    Estoy realizando un proyecto muy parecido al del post, solo que yo muestro noticias (por categorías) hasta ahí todo muy bien, pero he estado viendo otro de tus post específicamente este http://www.hermosaprogramacion.com/2015/07/tutorial-para-crear-un-gridview-en-android/, aquí al pulsar sobre un auto, te muestra otra Activity (ActivityDetalle) con la imagen del auto expandida, le estoy dando la misma funcionalidad a mi proyecto, ya me muestra la imagen de la noticia en la ActiviyDetalle, ahora quisiera también poder enviar un título, y el cuerpo de la noticia, he estado probando agregando en el mismo lugar donde obtengo la imagen_detalle (imagen_extendida en el proyecto Coches Nuevos)

    tituloDetalle = (TextView) findViewById(R.id.titulo_detalle);
    tituloDetalle.setText(itemDetallado.getTitulo());

    La ejecuto:
    Me sale error al momento de pulsar sobre la noticia para ver el detalle de la misma, debo hacer algo en el método onItemClick();??, como puedo enviar a la activityDetalle el titulo, porfa resuelveme la duda

    • Hola Richard.

      ¿Hablas de la transición?

      Esto se logra marcando con el atributo transitionName los views que deseas compartir. Debes marcarlos en ambos layouts.

      Además debes usar la clase Pair para enviar los tres views, algo como:

      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {

      ActivityOptionsCompat activityOptions =
      ActivityOptionsCompat.makeSceneTransitionAnimation(
      this,
      new Pair(view.findViewById(R.id.imagen_coche),
      ActividadDetalle.VIEW_NAME_HEADER_IMAGE),
      new Pair(view.findViewById(R.id.otroView),
      ActividadDetalle.NOMBRE_UNICO_1),
      new Pair(view.findViewById(R.id.otroView2),
      ActividadDetalle.NOMBRE_UNICO_2)
      );

      ActivityCompat.startActivity(this, intent, activityOptions.toBundle());
      }

      • Richard Daniel

        Gracias por tu ayuda, voy a probar y te comento, pero de antemano gracias por tomarte el tiempo para contestar

      • Richard Daniel

        Si, probé y funcionó, muchas gracias James, ha sido eso efectivamente

  • Richard Daniel

    Muy buen tutorial, se te agradece por el esfuerzo, tiempo y dedicación que te tomas al momento de compartir tus conocimientos, tengo una pequeña duda, la misma que se da al momento de cambiar el modo de visualización de vertical a horizontal(land-scape) y visceversa, bueno al hacerlo la app se detiene y se cierra, esto sale en el logCat

    • Hola Richard.

      ¿O sea, cuando rotas de portrait a landscape se te frena la app?

      … Si es así, fíjate que acabo de hacerlo y no me sucede.

      El error que pones en el logcat no tiene la descripción, podrías bajar un poco más para ver la otra parte?

      • Richard Daniel

        Si James se frena cuando rota, he revisado el logcat y no da una descripción del error, estoy probando las apps en un tablet de 10 pulg. no he probado en celular aún, pero no creo que el problema se presente por eso, con todo te adjunto la captura del error que sale cuando se rota la pantalla, capture toda la descripción

      • Richard Daniel

        Resulta que en ese dispositivo en específico donde hacía las pruebas se detenía la app al hacer la rotación, lo probé en el emulador y en otro dispositivo físico y no hay problemas.

        • Mm que raro Richard. Tal vez suceda porque la librería de diseño tiene algunos bugs.

          Igual, mantén tu SDK actualizado para ver si se incluyen soluciones para este caso.

          • Richard Daniel

            Un fuerte saludo James, sí nuevamente soy yo, es una pregunta apegada a este tema, mi duda es la siguiente:

            Estoy realizando un proyecto muy parecido al del post, solo que yo muestro noticias (por categorías) hasta ahí todo muy bien, pero he estado viendo otro de tus post específicamente este http://www.hermosaprogramacion.com/2015/07/tutorial-para-crear-un-gridview-en-android/, aquí al pulsar sobre un auto, te muestra otra Activity (ActivityDetalle) con la imagen del auto expandida, ahora yo quisiera darle la misma funcionalidad a la aplicación de las noticias (Que al pulsar sobre una se muestre la noticia completa), por le momento estoy haciendo únicamente como en el ejemplo para mostrar la imagen ( si sale con eso, ya muestro los demás detalles), he ido siguiendo los pasos paso a paso, cree el layout para la ActivityDetalle.java, modifique el Manifest.xml, todo muy bien.. ahora aquí entra mi duda y espero me puedas ofrecer tu ayuda:

      • Ramón Eugenio Carrillo Romero

        hola buena tarde, el error también me sale y veo que ocurre al crear el layout-land del archivo fragment_main.xml. Cuando se pasa al landscape y tratar de inflar en el metodo onCreateView.

        esta es la linea:
        View rootView = inflater.inflate(R.layout.fragment_main, container, false);

        deberia cargar el fragment_main de la carpeta layout-land, no se si el sistema tendria algun tipo de conflicto con decidir cual de los dos archivos cargar. si estoy equivocado por favor pido disculpas, seria bueno que nos resolvieran esta duda y asi dar la solucion a este error.

        Gracias

  • Cristopher Juan Greta Quispe

    Consulta, las aplicaciones que estas publicando son ¿Para que versiones de android ?

    • Hola Cristopher.

      Los estilos propiamente son para versiones mayores a 21. Pero se supone que la librería de diseño te da soporte hasta versiones inferiores.

      • Cristopher

        Excelente aporte, pero hay un problema, he agregado 5 tabs mas a este aplicativo que has subido, pero al parecer no tiene scroll y se muestra los TABS muy pegados

        • Cristopher en otro comentario te dije que si habias puesto el modo SCROLLABLE. Esto es necesario si son mas de tres.

  • edwin

    esta bueno el tutorial amigo gracias, todavía tengo mis dudas si se puede hacer un TabLayout sin mostrar los titulos , no se exactamente como se llama el view cuando se pasa de una actividad a otra y unos circulos aparecen abajo muy pequeños

    • James Revelo

      Mmm creo, que se llama DotsPagerIndicator para android wear, pero no lo he visto para teléfonos. Supongo que se debe crear un layout personalizado o usar una librería.

  • Juan Carlos

    Excelente artículo. Hace tiempo que estoy liado con Tabs en una aplicación y me ha solucionado algunas dudas pero no todas. ¿Me podrias orientar un poco con mi problema? Te explico mi caso. Tengo mi aplicación con 3 Tabs. Cada tab tiene su propio fragment (a diferencia de tu ejemplo) y necesitan recibir los datos de la posición GPS actual. La aplicación principal es la que se encarga de obtener la posición del GPS (no creo que lo tenga que implementar en cada fragment) y enviar cada vez que se cambian las coordenadas la información al tab activo para tratar la información. Y es en este punto donde estoy perdido: que el tab visible reciba constantemente información y que detecte la nueva información para tratarla. ¿Alguna idea/consejo para poder seguir avanzando?

    Excelente trabajo y gracias.

    • James Revelo

      Hola Juan.

      Mmm bueno, se me ocurre que setees una escucha OnPageChangeListener en el ViewPager y sobrescribas el controlador onPageSelected(). Este callback recibe la posición de la sección donde se encuentra el view pager actualmente.

      Supongo que dentro de este método usarías un switch para procesar las tres pestañas y actualizar los datos.

      Es algo que se me ocurre a vuelo de pájaro, sin embargo prueba a ver como te va.

      En este artículo que hice tiempo atrás puedes ver como setear la escucha: http://www.hermosaprogramacion.com/2014/12/android-action-bar-tabs/

      Saludos!

  • Guillermo Valle

    Saludos.
    Los conocí por un tutorial sobre hacer una aplicación desde cero. Soy desarrollador y capacitador en aplicaciones móviles desde hace algunos años. Escribo para comentarles que considero que hacen una gran labor en sus articulos y tutoriales. Pero quiero hacer una sugerencia importante que creo puede ayudar a mejorar el contenido y de hecho he tenido oportunidad de tomar cursos y parece ser que pocos de quienes intentan hacer tutoriales o cursos también lo comprenden. En concreto la mayoría de ideas sobre hacer cursos (y ocurre en general) de programación parece muy enfocada a escribir código nada más y olvidarse de hacer un trabajo de análisis y diseño previo. Sus tutoriales que recibo todos sólo van indicando paso por paso que quieren que escriban en cuanto a código las personas. No hay una razón sintetizada sobre como esta estructurada la aplicación, que paradigma de análisis o patrones de diseño sigue. De forma sencila diré que sólo indicar “escribe esto y paso 2 escribe lo otro” no le da a las personas un sentido de qué hacen y porque lo hacen. Casualmente en algún paso dicen porque se hará, pero en general no lo hacen. Sería bueno en sus tutoriales y en general describir con detalles de análisis y diseño lo que se estará escribiendo en el código y no sólo como un corderito escribir código sin más. Esta es una sugerencia constructiva que espero tomen en cuenta. Gracias y buena suerte

    • James Revelo

      Hola Guillermo. Gracias por tomarte el tiempo de escribir tan excelente apreciación.

      Claro que te doy la razón, es importante que los lectores puedan comprender los fundamentos para la creación de las apps.

      Algunos tutoriales de mi web tienen ese tipo de análisis y diseño del que hablas, debido a que me enfoco en explicar una app con algún propósito (ya sea un lector rss, un servicio web, etc).

      Por otro lado, hay otros tutoriales que explican un tema concreto y se enfocan en explicar una sola característica. Aunque creo y comparto una app de ejemplo donde integro la solución, no explico como fue el flujo de trabajo para concebirla. Esto se debe a que el propósito es mostrar una implementación técnica que justamente se enfoca al código. Por ello asumo que hay conocimientos previos acumulados.

      Sin embargo, tendré en cuenta tu recomendación amigo. Saludos :D

      • Jose Delgado

        Hola Srs. James y Guillermo. En mi opinión, hasta el momento los tutoriales son muy claros y funcionales, por lo general yo los leo, codifico, pruebo por partes y último descargo para comparar, si se te pasa algo, y no se entiende una parte del código, el camino a seguir es buscar en la documentación y/o googleas esa parte, o qué opinan ?. Bueno para alguien que recién empieza o esta formando su base de conocimientos, deberá decidir si quiere irse por el camino fácil de solo copiar y pegar y correr la aplicación y o el difícil tipear (obvio no es tan rápido como copiar y pegar pero te sirve para probar partes del código, si deseas usas git para hacer tus commits) pero con eso no solo basta y solo estas memorizando más no avanzas, porque Android y otras tecnologías evolucionan constantemente, además te tiene que gustar investigar, buscar sin limitarte por el idioma y lo primero y más importante entender el problema y todo el proceso del análisis y el flujo del diseño y de la codificación. Algunos consejos adicionales que me podrían dar ? Gracias.

        • James Revelo

          Hola Jose.

          Tu tienes razón, investigar es parte del proceso de aprendizaje de la programación. Generar la actitud de autonomía ante los conocimientos desarrolla la capacidad para solucionar problemas.