En la Guía 4 vamos a aprender a crear un Custom ListView Item, es decir, a personalizar los elementos de nuestras listas para mostrar una información más rica y adaptada a nuestras necesidades.

Antes de continuar la lectura de la guía, quizá te interese leer la anterior, en la que aprendíamos a crear una vista con un ListView. Partiremos del proyecto de la Guía 3 para los ejemplos de la nueva Guía.

¿Qué vamos a hacer?

Vamos a continuar el proyecto de la Guia 3, en el que teníamos una lista de títulos de libro, de forma que ahora la lista muestre para cada libro: una imagen, un titulo y su autor.

Paso 1 - Preparar nuestro Layout lista_item.xml

En la Guía 3 estábamos mostrando en el ListView una lista de títulos (cadenas de texto) cada uno de los cuales necesitaba un único TextView para representar esa información. Es por eso el Layout lista_item.xml, que es el diseño de cada elemento de la lista, solo contenia un control TextView.

En el ejemplo de hoy vamos a mostrar una imagen, un campo de texto para el título, y un campo de texto para el nombre del autor. Por lo tanto, necesitaremos un control ImageView para la imagen, y dos TextView para el titulo y autor.

Como siempre que vamos a colocar más de un control en el layout, necesitaremos tambien controles contenedores para anidarlos.

Despues de editar nuestro archivo lista_item.xml quedará algo así:

<?xml version="1.0" encoding="utf-8"?>  
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
android:layout_width="fill_parent"  
android:layout_height="?android:attr/listPreferredItemHeight"  
android:padding="5dip">  
<ImageView  
android:id="@+id/icono"  
android:layout_width="wrap_content"  
android:layout_height="fill_parent"  
android:layout_marginRight="5dip"  
android:src="@drawable/icon" />  
<LinearLayout  
android:orientation="vertical"  
android:layout_width="0dip"  
android:layout_weight="1"  
android:layout_height="fill_parent">  
<TextView  
android:id="@+id/titulo"  
android:layout_width="fill_parent"  
android:layout_height="0dip"  
android:layout_weight="1"  
android:gravity="center_vertical"  
/>  
<TextView  
android:layout_width="fill_parent"  
android:layout_height="0dip"  
android:layout_weight="1"  
android:id="@+id/autor"  
android:singleLine="true"  
android:ellipsize="marquee"  
/>  
</LinearLayout>  
</LinearLayout>

Si nos fijamos bien, veremos que lo que hemos hecho es definir un LinearLayout, que por defecto mostrara los elementos que contenga uno al lado del otro, dentro tenemos un ImageView, que por lo tanto aparecerá a la izquierda, y otro LinearLayout, que aparecerá a continuación de la imagen, a su derecha. Este segundo LinearLayout tiene orientación vertical, por lo que los dos TextView que contiene aparecerán uno encima de otro (y a la derecha de la imagen). El resto de atributos sirven para personalizar aún más el diseño, como son alturas de los elementos, margenes, etc, pero ahora mismo podemos obviarlos.

Paso 2 - Preparar los objetos que aparecerán en la lista

En la Guía 3, el control ListView recibía una lista de títulos (cadenas de texto), para lo que usábamos un _ArrayList._ Para el ejemplo de hoy necesitamos que nuestra entidad **Libro** contenga más información: un titulo y su autor, ya que esta entidad será lo que vamos a mostrar en cada celda de nuestro ListView.

En nuestro archivo ControladorLista.java, donde tenemos implementada la Actividad que controla la lista, vamos a crear a nivel de clase, una entidad Libro, con dos propiedades.

public class Libro {  
private String Titulo = "";  
private String Autor = "";

public String getTitulo() {  
return Titulo;  
}

public String getAutor() {  
return Autor;  
}

public void setTitulo(String titulo) {  
Titulo = titulo;  
}

public void setAutor(String autor) {  
Autor = autor;  
}  
}  

También vamos a modificar el método getItems() para que genere y devuelva una lista de entidades Libro.

/*  
* Obtiene una lista de libros  
*  
* @returns ArrayList libros  
*/  
public ArrayList<Libro> getItems() {  
ArrayList<Libro> MiLista = new ArrayList<Libro>();

// Creamos los objetos Libro  
Libro libro1 = new Libro();  
libro1.setTitulo("El Silmarillion");  
libro1.setAutor("J.R.R. Tolkien");

Libro libro2 = new Libro();  
libro2.setTitulo("El Señor de los Anillos");  
libro2.setAutor("J.R.R. Tolkien");

Libro libro3 = new Libro();  
libro3.setTitulo("Los propios dioses");  
libro3.setAutor("Isaac Asimov");

// Añadimos los libros a la lista  
MiLista.add(libro1);  
MiLista.add(libro2);  
MiLista.add(libro3);

return MiLista;  
}  

Paso 3 - Implementar un CustomAdapter para el ListView

En la Guía 3, vimos que para que el ListView poblase sus elementos a partir de una lista de cadenas de texto, bastaba con invocar el método setListAdapter al que pasabamos un **ArrayAdapter** y el recurso que iba a actuar como layout para los elementos de nuestra lista.

Este paso era así de sencillo porque el adaptador por defecto de la lista, al recibir una lista de Strings, y teniendo en su Layout un unico TextView, sabe facilmente que lo que debe hacer es usar el TextView para mostrar el String correspondiente. Pero hoy queremos mostrar una entidad Libro, que es mas compleja, por lo que tendremos que implementar nuestro propia clase Adapter, a la que llamaremos LibroAdapter, y que **heredará de ArrayAdapter**, donde podremos decirle en que control de Layout debe mostrar cada atributo del objeto Libro.

private class LibroAdapter extends ArrayAdapter<Libro> {

private ArrayList<Libro> items;

public LibroAdapter(Context context, int textViewResourceId, ArrayList<Libro> items) {  
super(context, textViewResourceId, items);  
this.items = items;  
}

@Override  
public View getView(int position, View convertView, ViewGroup parent) {  
View v = convertView;  
if (v == null) {  
LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);  
v = vi.inflate(R.layout.lista_item, null);  
}  
Libro libro = items.get(position);  
if (libro != null) {  
TextView ttitulo = (TextView) v.findViewById(R.id.titulo);  
TextView tautor = (TextView) v.findViewById(R.id.autor);  
if (ttitulo != null) {  
ttitulo.setText(libro.getTitulo());  
}  
if (tautor != null) {  
tautor.setText(libro.getAutor());  
}  
}  
return v;  
}  
}  

Prestemos atención a lo que estamos haciendo:

  • En el constructor, recibimos los parametros que requiere nuestra clase padre ArrayAdapter: contexto y layout donde mostraremos cada elemento de la lista.
  • También recibimos dicha lista (a la que llamaremos items), y la almacenamos en una variable a nivel de clase de tipo **ArrayList.**
  • Sobreescribimos el método getView() de la clase padre ArrayAdapter. Este método se ejecutará una vez por cada elemento que vayamos a mostrar en la lista, y recibirá el indice de dicho elemento en la lista en el parametro posicion. Es decir, si en la lista cargamos 3 elementos, este método se ejecutará 3 veces, uno por cada elemento, y se encarga de preparar y cargar la información en el Layout de nuestros elementos, es decir lista_item.xml. Por lo tanto lo que haremos en este método será en líneas generales, obtener el Libro que corresponde a la llamada actual, obtener los controles TextView del Layout en los que vamos a mostrar la información, y cargar el atributo del Libro apropiado en cada uno.

Nota: Además, bajo ciertas circunstancias, aunque nuestra lista lista solo tenga por ejemplo 3 elementos, para representar la vista en el dispositivo, el sistema puede requerir la ejecución de este método 3*N veces. Siendo N un valor variable que depende de la anidación de controles en nuestra vista y del atributo layout_width y layout_height de los mismos, si deben calcularse dinamicamente (con wrap_content por ejemplo). Pero ya veremos la solución a este problema en futuros posts.

Paso 4 - Poblar la lista con los objetos Libro

Una vez que tenemos esto, solo falta cargar la lista de Libros en el adaptador de la lista, en el evento onCreate de la Actividad.

Para esto empleamos como siempre el método setListAdapter, al que pasamos la lista de libros, pero esta vez usaremos nuestro adaptador personalizado.

/*\* Called when the activity is first created. \*/  
@Override  
public void onCreate(Bundle savedInstanceState) {  
super.onCreate(savedInstanceState);

// Obtenemos la lista de Libros  
ArrayList<Libro> Libros = getItems();  
// Entregamos la lista de Libros al adaptador de la lista en el Layout Lista.xml  
setListAdapter(new LibroAdapter(this, R.layout.lista_item, Libros));  
}  
Paso 5 - Ejecutar la aplicación

Nuestra Actividad controladora de la Lista, después de todos estos cambios, queda así (pulsa para desplegar):

package com.example.tolkienlibrary;

import java.util.ArrayList;

import android.app.ListActivity;  
import android.content.Context;  
import android.os.Bundle;  
import android.view.LayoutInflater;  
import android.view.View;  
import android.view.ViewGroup;  
import android.widget.ArrayAdapter;  
import android.widget.TextView;

public class ControladorLista extends ListActivity {

public class Libro {  
private String Titulo = "";  
private String Autor = "";

public String getTitulo() {  
return Titulo;  
}

public String getAutor() {  
return Autor;  
}

public void setTitulo(String titulo) {  
Titulo = titulo;  
}

public void setAutor(String autor) {  
Autor = autor;  
}  
}

private class LibroAdapter extends ArrayAdapter<Libro> {

private ArrayList<Libro> items;

public LibroAdapter(Context context, int textViewResourceId, ArrayList<Libro> items) {  
super(context, textViewResourceId, items);  
this.items = items;  
}

@Override  
public View getView(int position, View convertView, ViewGroup parent) {  
View v = convertView;  
if (v == null) {  
LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);  
v = vi.inflate(R.layout.lista_item, null);  
}  
Libro libro = items.get(position);  
if (libro != null) {  
TextView ttitulo = (TextView) v.findViewById(R.id.titulo);  
TextView tautor = (TextView) v.findViewById(R.id.autor);  
if (ttitulo != null) {  
ttitulo.setText(libro.getTitulo());  
}  
if (tautor != null) {  
tautor.setText(libro.getAutor());  
}  
}  
return v;  
}  
}

/*\* Called when the activity is first created. \*/  
@Override  
public void onCreate(Bundle savedInstanceState) {  
super.onCreate(savedInstanceState);

// Obtenemos la lista de Libros  
ArrayList<Libro> Libros = getItems();  
// Entregamos la lista de Libros al adaptador de la lista en el Layout Lista.xml  
setListAdapter(new LibroAdapter(this, R.layout.lista_item, Libros));  
}

/*  
* Obtiene una lista de libros  
*  
* @returns ArrayList<Libro> libros  
*/  
public ArrayList<Libro> getItems() {  
ArrayList<Libro> MiLista = new ArrayList<Libro>();

// Creamos los objetos Libro  
Libro libro1 = new Libro();  
libro1.setTitulo("El Silmarillion");  
libro1.setAutor("J.R.R. Tolkien");

Libro libro2 = new Libro();  
libro2.setTitulo("El Señor de los Anillos");  
libro2.setAutor("J.R.R. Tolkien");

Libro libro3 = new Libro();  
libro3.setTitulo("Los propios dioses");  
libro3.setAutor("Isaac Asimov");

// Añadimos los libros a la lista  
MiLista.add(libro1);  
MiLista.add(libro2);  
MiLista.add(libro3);

return MiLista;  
}  
}  

Y con esto hemos acabado.

Paso 6 - Descarga el proyecto

Puedes descargar el código fuente del proyecto completo que hemos realizado en ésta guía desde aquí

Paso 7 - Aún hay más…

Si queréis hacer una lista aún más avanzada, y usar un fragmento para distinguir y destacar completamente la funcionalidad y el aspecto de vuestro primer elemento de la lista (algo muy habitual) podeis ver la guia aquí:

Incluir un Fragmento en una ListView

O podéis leer las otras guías:

Guia 1 - Hola Mundo en Android.

Guia 2 - Navegación entre Layouts con un botón, y paso de parámetros.

Guia 3 - ListView o listas con scroll.

Guia 4 - ListView ricas con celdas personalizadas e imágenes.

Guia 5 - Acceso a base de datos (SQLLite).