Tutorial Para Crear Un GridView En Android

Similar al ListView, un GridView en Android es un view que contiene otros views en forma de grilla o cuadricula, cuya orientación puede ser vertical u horizontal.

El mayor uso de este elemento lo puedes ver en las galerías de imágenes.  Donde se proyectan imágenes de forma uniforme para mejorar la lectura del contenido.

Precisamente en este artículo crearemos un ejemplo de gridview con imágenes de coches nuevos.

Adicionalmente verás cómo implementar la escucha OnItemClickListener para leer las celdas presionadas y ver su detalle en otra actividad.

Descargar Proyecto Android Studio De «Coches Nuevos»

Si deseas ver el resultado final de lo que aprenderás en este tutorial, el siguiente video habla por sí solo:

Para desbloquear el link de descarga del código completo sigue estas instrucciones:

[sociallocker id=»7121″]Descargar Gratis[/sociallocker]

Creando Un GridView

Antes de comenzar a trabajar con gridviews en Android ten en cuenta el estilo visual que los lineamientos del Material Design han establecido.

La forma en que debe lucir una Grid List —Así le llama Google en este apartado— en Android depende del propósito de tu aplicación. Sin embargo hay especificaciones generales como:

  • Considerar la forma de presentación de los elementos de izquierda a derecha. Esto significa que el primer elemento del grid view es el que se encuentra en la parte superior izquierda.
    Escaneo De Una Grid List En Material Design
  • Usar solo scrolling vertical para escanear los elementos. El único caso en donde es viable usar el scrolling horizontal es en las secciones donde se necesita una sola línea horizontal para mostrar una galería de objetos relacionados o similares.
    Scrolling Vertical En Una Grid List Material Design
  • Evita usar como foco inicial el último elemento del gridview. El scrolling debe comenzar desde el primer ítem para dar a entender que existe más contenido en la parte inferior.
    GridView Scrolling Inicial Al Final

Programáticamente un grid view se representa en Java con la clase GridView. Para poblar sus elementos es necesario usar un adaptador del tipo ListAdapter o sobrescribir la clase BaseAdapter para personalizar los elementos.

Ejemplo De GridView Con Imágenes Y Texto Por Debajo

Paso 1. Dentro de Android Studio ve a File > New > New Project… para crear un nuevo proyecto. Nómbralo «Coches Nuevos» y añade una actividad en blanco llamada ActividadPrincipal.java.

Paso 2. Ve a res/values/strings.xml y agrega las siguientes cadenas para soportar el texto en nuestra aplicación.

strings.xml

<resources>
    <string name="app_name">Coches Nuevos</string>

    <string name="action_settings">Settings</string>

    <string name="titulo_actividad_detalle">Actividad De Detalle</string>

    <string name="nombre_transicion_imagen">imagen_compartida</string>
</resources>

Paso 3. Crea un nuevo archivo para recursos de color dentro de res/values llamado colors.xml y agrega la paleta completa para el soporte de Material Design.

colors.xml

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

    <color name="darkPrimaryColor">#7B1FA2</color>
    <color name="primaryColor">#9C27B0</color>
    <color name="accentColor">#00BCD4</color>

    <color name="backgroundColor">#212121</color>

    <color name="background_footer">#90000000</color>
</resources>

Paso 4. Lo siguiente es editar el estilo general de la aplicación. Abre el archivo styles.xml y agrega la siguiente definición.

styles.xml

<resources>

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

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

</resources>

En este ejemplo usaremos transiciones entre actividades, así que crea una variación para los estilos cuyo calificador apunte a las versiones de Android mayores a 21.

values-21/styles.xml

<resources>

    <style name="AppTheme" parent="Base.AppTheme">
        <item name="android:windowContentTransitions">true</item>
    </style>

</resources>

Paso 5. Descarga las imágenes que usaremos dentro del Grid View y ponlas en la carpeta res/drawable.

Crear Activity Principal De La Aplicación

Paso 6. Ahora abre el layout que implementa la interfaz de la actividad principal y añade la siguiente implementación.

actividad_principal.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical">

    <android.support.v7.widget.Toolbar
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:id="@+id/toolbar"
        android:background="?attr/colorPrimary"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
        app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />

    <GridView
        android:id="@+id/grid"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:columnWidth="120dp"
        android:horizontalSpacing="@dimen/espacio_grilla"
        android:numColumns="auto_fit"
        android:padding="@dimen/espacio_grilla"
        android:verticalSpacing="@dimen/espacio_grilla" />
</LinearLayout>

El diseño anterior tiene como raíz un elemento <LinearLayout>, ya que necesitamos añadir la Toolbar a la actividad en la parte superior. En la parte inferior se ubicará nuestro GridView para crear la cuadrícula de imágenes de coches que veremos.

Algunos atributos importantes que debes tener en cuenta:

Atributos De Un GridView En Android

  • columnWidth: Especifica el ancho de cada columna de la cuadrícula.
  • gravity: Determina la ubicación del contenido de cada celda. Puedes usar constantes como top, bottom, left, right, etc.
  • horizontalSpacing: Define el espacio horizontal entre columnas. Aquí puedes usar un rango entre 1-4 dps.
  • numColumns: Indica el número de columnas que habrá. Puedes usar el valor de la constante auto_fit para indicarle al framework que infle la cantidad necesaria de columnas según el atributo columnWidth.
  • stretchMode: Dictamina como se estirará el espacio sobrante entre columnas. Usa el valor none para no especificar modo; spacingWidth para estirar el ancho de las columnas sin mantener proporción; columnWidth para estirar el ancho de las columnas de forma igualada y spacingWidthUniform para estirar de forma uniforme.
  • verticalSpacing: Determina el espacio vertical entre las filas.

Crear Un BaseAdapter Para El GridView

Paso 7. Antes que nada, representa cada coche con la clase Coche.java para tener una fuente de datos. Debido a que solo usaremos la imagen y el nombre de cada elemento, entonces agregaremos estos dos atributos.

Coche.java

/**
 * Clase que representa la existencia de un Coche
 */
public class Coche {
    private String nombre;
    private int idDrawable;

    public Coche(String nombre, int idDrawable) {
        this.nombre = nombre;
        this.idDrawable = idDrawable;
    }

    public String getNombre() {
        return nombre;
    }

    public int getIdDrawable() {
        return idDrawable;
    }

    public int getId() {
        return nombre.hashCode();
    }

    public static Coche[] ITEMS = {
            new Coche("Jaguar F-Type 2015", R.drawable.jaguar_f_type_2015),
            new Coche("Mercedes AMG-GT", R.drawable.mercedes_benz_amg_gt),
            new Coche("Mazda MX-5", R.drawable.mazda_mx5_2015),
            new Coche("Porsche 911 GTS", R.drawable.porsche_911_gts),
            new Coche("BMW Serie 6", R.drawable.bmw_serie6_cabrio_2015),
            new Coche("Ford Mondeo", R.drawable.ford_mondeo),
            new Coche("Volvo V60 Cross Country", R.drawable.volvo_v60_crosscountry),
            new Coche("Jaguar XE", R.drawable.jaguar_xe),
            new Coche("VW Golf R Variant", R.drawable.volkswagen_golf_r_variant_2015),
            new Coche("Seat León ST Cupra", R.drawable.seat_leon_st_cupra),
    };

    /**
     * Obtiene item basado en su identificador
     *
     * @param id identificador
     * @return Coche
     */
    public static Coche getItem(int id) {
        for (Coche item : ITEMS) {
            if (item.getId() == id) {
                return item;
            }
        }
        return null;
    }
}

A diferencia de los atributos tenemos otras piezas de código fundamentales:

  • El arreglo ITEMS contiene 10 objetos coche de prueba que representarán el proveedor de datos para nuestro adaptador.
  • El método getId() retorna el identificador hash de cada ítem con referencia al código. Más adelante veremos cuál es su utilidad.
  • El método getItem() obtiene un ítem del arreglo basado en su código hash.

Paso 8. Antes de crear el adaptador, es necesario definir el diseño de cada ítem. Podemos tomar como referencia los elementos con un footer de una sola línea.

GridView En Android Con Texto Por Debajo

En este caso el padding en todos los bordes es de 16dp y el texto tiene 16sp de dimensión.

Para ello usamos un ImageView en la parte superior y un TextView en el lado inferior como se muestra a continuación:

grid_item.xml

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

    <ImageView
        android:id="@+id/imagen_coche"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="centerCrop"
        android:transitionName="@string/nombre_transicion_imagen" />

    <TextView
        android:id="@+id/nombre_coche"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:background="@color/background_footer"
        android:maxLines="1"
        android:padding="16dp"
        android:textAppearance="@style/TextAppearance.AppCompat.Subhead"
        android:textColor="@android:color/white" />

</FrameLayout>

Como notas, el FrameLayout tiene una altura de 200dp para contener ambos elementos.

Ya que usaremos transiciones de elementos compartidos para el ImageView, debemos marcarlo con el atributo transitionName.

Para el texto del footer usamos un color de fondo gris oscuro con algo de transparencia. El estilo del texto podemos copiarlo de TextAppearance.AppCompat.Subhead.

Item De GridView Con Imagen Y Texto

Paso 9. Ahora crea el adaptador del gridview añadiendo una nueva clase con el nombre de AdaptadorDeCoches.java. Esta extenderá de BaseAdapter para personalizar el inflado.

AdaptadorDeCoches.java

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.TextView;

/**
 * {@link BaseAdapter} para poblar coches en un grid view
 */

public class AdaptadorDeCoches extends BaseAdapter {
    private Context context;

    public AdaptadorDeCoches(Context context) {
        this.context = context;
    }

    @Override
    public int getCount() {
        return Coche.ITEMS.length;
    }

    @Override
    public Coche getItem(int position) {
        return Coche.ITEMS[position];
    }

    @Override
    public long getItemId(int position) {
        return getItem(position).getId();
    }

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

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

        ImageView imagenCoche = (ImageView) view.findViewById(R.id.imagen_coche);
        TextView nombreCoche = (TextView) view.findViewById(R.id.nombre_coche);

        final Coche item = getItem(position);
        imagenCoche.setImageResource(item.getIdDrawable());
        nombreCoche.setText(item.getNombre());

        return view;
    }

}

Este adaptador es muy similar a los que hemos usado en las listas. Sabemos que getCount() informa sobre el número de elementos a poblar, por ende usamos el atributo length del arreglo de la clase Coche.

Cada elemento se obtiene con getItem() y la posición del elemento dentro del arreglo.

getItemId() proporciona el identificador del elemento, por lo que podemos usar el método getId()que construimos anteriormente. Este valor es indispensable para implementar eventos en el GridView.

Finalmente dentro de getView() inflamos cada view con los datos del coche con la posición otorgada en el primer parámetro position. Basta con obtener los views para la imagen y el nombre y luego asignar los valores correspondientes.

Ejecuta el proyecto para ver el siguiente resultado.

Aplicación Android Con GridView De Coches

No obstante, al probar el scrolling se nota una ralentización en los movimientos. Y es que la cantidad de imágenes y su tamaño están entorpeciendo la fluidez del hilo principal. Incluso el siguiente mensaje puede estar apareciendo en tu logcat.

Mensaje Too Much Work On Its Main Thread

Esto se debe a que no estamos mostrando las imágenes de forma eficiente. Para ello se requieren muchos aspectos como:

  • Usar hilos a parte para la transformación de los Bitmaps.
  • Almacenar imágenes en caché para evitar trabajos innecesarios.
  • Evitar múltiples trabajos repetidos por el adaptador.
  • Construcción de miniaturas para ahorrar carga.
  • Salvaguardar los procesos ante eventos de cambio de configuración.
  • Algunos más…

Lo bueno es que contamos con una solución: La librería Glide. Esta herramienta gestiona de forma automatizada muchos de los aspectos anteriores en la carga de imágenes. Así que al implementarla veremos como la aplicación fluye mucho mejor.

Logo De La Librería Glide Para Android

Paso 10 . Añade en tu archivo build.gradle la siguiente dependencia para habilitar Glide.

build.gradle

dependencies {
    ...
    compile 'com.github.bumptech.glide:glide:3.6.1'
}

Paso 11. Abre la clase AdaptadorDeCoches.java y modifica la línea de asignación de imagen el método getView() por la siguiente.

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

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

    ImageView imagenCoche = (ImageView) view.findViewById(R.id.imagen_coche);
    TextView nombreCoche = (TextView) view.findViewById(R.id.nombre_coche);

    final Coche item = getItem(position);
    Glide.with(imagenCoche.getContext())
            .load(item.getIdDrawable())
            .into(imagenCoche);

    nombreCoche.setText(item.getNombre());

    return view;
}

La carga sobre el ImageView se realiza con el método Glide.load(), el cual recibe el identificador del drawable. Luego especificamos el objeto donde deseamos depositar el contenido con Glide.into().

Corre de nuevo el proyecto y percibe el cambio.

Crear Activity Para El Detalle

Paso 12. Tenido tu paquete Java seleccionado, dirígete a File >New > Activity > Blank Activity. Establece el nombre de «ActividadDetalle» y regístrala como hija de ActividadPrincipal a través del campo Hierarchical Parent.

Paso 13. Edita el layout de la actividad de detalle para añadir la Toolbar y un ImageView que recubra todo el espacio sobrante.

actividad_detalle.xml

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

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
        app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />

    <ImageView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/imagen_extendida"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:transitionName="@string/nombre_transicion_imagen">

    </ImageView>
</LinearLayout>

La idea es que la imagen del ítem presionado en el grid view se proyecte completa en esta actividad. Recuerda usar el atributo transitionName para efectuar la transición.

Paso 14. Abre ActividadDetalle.java y carga la imagen con Glide sobre el ImageView. Obviamente debes saber con antelación el identificador del ítem. Esto se hará con el valor extra que sea enviado a través de un Intent desde ActividadPrincipal.

ActividadDetalle.java

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.widget.ImageView;

import com.bumptech.glide.Glide;

/**
 * Actividad que muestra la imagen del item extendida
 */
public class ActividadDetalle extends AppCompatActivity {

    public static final String EXTRA_PARAM_ID = "com.herprogramacion.coches2015.extra.ID";
    public static final String VIEW_NAME_HEADER_IMAGE = "imagen_compartida";
    private Coche itemDetallado;
    private ImageView imagenExtendida;

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

        usarToolbar();

        // Obtener el coche con el identificador establecido en la actividad principal
        itemDetallado = Coche.getItem(getIntent().getIntExtra(EXTRA_PARAM_ID, 0));

        imagenExtendida = (ImageView) findViewById(R.id.imagen_extendida);

        cargarImagenExtendida();
    }

    private void cargarImagenExtendida() {
        Glide.with(imagenExtendida.getContext())
                .load(itemDetallado.getIdDrawable())
                .into(imagenExtendida);
    }

    private void usarToolbar() {
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
    }
}

Añadir Escucha OnItemClickListener Al GridView

Paso 15. Haz que ActividadPrincipal implemente la escucha OnItemClickListener. Como ya sabes, esta será la que procese los eventos para obtener el ítem seleccionado del GridView.

Luego de eso sobrescribe el método onItemClick() para iniciar la actividad de detalle del componente elegido.

ActividadPrincipal.java

import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat;
import android.support.v4.util.Pair;
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 android.widget.AdapterView;
import android.widget.GridView;

public class ActividadPrincipal extends AppCompatActivity 
        implements AdapterView.OnItemClickListener {
    private GridView gridView;
    private AdaptadorDeCoches adaptador;

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

        usarToolbar();

        gridView = (GridView) findViewById(R.id.grid);
        adaptador = new AdaptadorDeCoches(this);
        gridView.setAdapter(adaptador);
        gridView.setOnItemClickListener(this);
    }

    private void usarToolbar() {
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

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

        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        Coche item = (Coche) parent.getItemAtPosition(position);

        Intent intent = new Intent(this, ActividadDetalle.class);
        intent.putExtra(ActividadDetalle.EXTRA_PARAM_ID, item.getId());

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

            ActivityOptionsCompat activityOptions =
                    ActivityOptionsCompat.makeSceneTransitionAnimation(
                            this,
                            new Pair<View, String>(view.findViewById(R.id.imagen_coche),
                                    ActividadDetalle.VIEW_NAME_HEADER_IMAGE)
                    );

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

}

En onCreate() obtuvimos la instancia del grid view y creamos el adaptador. Ambos se relacionaron y a su vez seteamos la escucha con setOnItemClickListener().

En el método onItemClick() se obtiene el objeto seleccionado del grid view. Luego se prepara un intent con su identificador.

Este intent es enviado en el inicio de la actividad de detalle dependiendo de la versión de Android. Si es mayor o igual a LOLLIPOP, entonces usamos transiciones. De lo contrario iniciamos normalmente con startActivity().

Paso 16. Finalmente corre el proyecto y verás el siguiente resultado.

Aplicación Android Con Imagen De Coche

Conclusión

En este tutorial vimos un ejemplo de gridview con imágenes y texto en Android, con el fin de comprender como poblarlo con un adaptador y manejar eventos con la escucha OnItemClickListener.

Aunque la aplicación es básica, es una base para que elabores interfaces mucho más elaboradas acompañadas del Material Design.

La fuente de datos que usamos para las imágenes es estática, sin embargo es posible ampliar la funcionalidad para cargar imágenes desde la nube.

Puedes aprender a realizar peticiones http a un servidor para consultar un servicio web que retorne la información en formato Json y cargar las imágenes con esos datos. Sobre todo recordando que es necesario aislar la carga de las imágenes en un hilo de trabajo para mejorar la respuesta de la interfaz de usuario.

Únete Al Discord De Develou

Si tienes problemas con el código de este tutorial, preguntas, recomendaciones o solo deseas discutir sobre desarrollo Android conmigo y otros desarrolladores, únete a la comunidad de Discord de Develou y siéntete libre de participar como gustes. ¡Te espero!