En el ejercicio anterior hemos visto como podíamos asociar un Layout definido por nosotros al ListView y personalizar uno de sus campos. Si queremos algo más adaptable, por ejemplo cambiar varios campos, tendremos que escribir nuestro propio adaptador extendiendo la clase BaseAdapter.

Para crear un descendiente de BaseAdapter has de sobrescribir los siguientes cuatro métodos:

  • View getView(int position, View convertView, ViewGroup parent) Este método ha de construir un nuevo objeto View que corresponda a la posición position . Opcionalmente podemos partir de una vista base convertView para generar más rápido  este objeto. El último parámetro corresponde al contenedor de vistas donde el objeto va a ser añadido.
  • int getCount() Devuelve el número de elementos de la lista. 
  • Object getItem(int position) Devuelve el elemento en una determinada posición de la lista.
  • long getItemId(int position) Devuelve el identificador de fila de una determinada posición de la lista.

Veamos un ejemplo:

Ejercicio paso a paso: Un ListView con nuestro propio adaptador

1. Crea la clase AdaptadorLugares en el proyecto con el siguiente código:

public class AdaptadorLugares extends BaseAdapter { 
     private LayoutInflater inflador; // Crea Layouts a partir del XML
     TextView nombre, direccion;
     ImageView foto;
     RatingBar valoracion;
     public AdaptadorLugares(Context contexto) {
          inflador =(LayoutInflater)contexto
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
     }
     public View getView(int posicion, View vistaReciclada, 
                                        ViewGroup padre) {
          Lugar lugar =MainActivity.lugares.elemento(posicion);
          if (vistaReciclada == null) {
               vistaReciclada= inflador.inflate(R.layout.elemento_lista, null);
          }
          nombre = (TextView) vistaReciclada.findViewById(R.id.nombre);
          direccion = (TextView) vistaReciclada.findViewById(R.id.direccion);
          foto = (ImageView) vistaReciclada.findViewById(R.id.foto);
          valoracion = (RatingBar) vistaReciclada.findViewById(R.id.valoracion);
          nombre.setText(lugar.getNombre());
          direccion.setText(lugar.getDireccion());
          int id = R.drawable.otros;
          switch(lugar.getTipo()) {
            case RESTAURANTE:id = R.drawable.restaurante; break;
            case BAR:        id = R.drawable.bar;         break;
            case COPAS:      id = R.drawable.copas;       break;
            case ESPECTACULO:id = R.drawable.espectaculos; break;
            case HOTEL:      id = R.drawable.hotel;       break;
            case COMPRAS:    id = R.drawable.compras;     break;
            case EDUCACION:  id = R.drawable.educacion;   break;
            case DEPORTE:    id = R.drawable.deporte;     break;
            case NATURALEZA: id = R.drawable.naturaleza;  break;
            case GASOLINERA: id = R.drawable.gasolinera;  break;
          }
          foto.setImageResource(id);
          foto.setScaleType(ImageView.ScaleType.FIT_END);
          valoracion.setRating(lugar.getValoracion());  
          return vistaReciclada;
     }

     public int getCount() {
          return MainActivity.lugares.size();
     }
     public Object getItem(int posicion) {
          return MainActivity.lugares.elemento(posicion);
     }

     public long getItemId(int posicion) {
          return posicion;
     }
}

En el constructor de la clase creamos un inflater en el objeto inflador. Un inflater es una herramienta que nos permite crear un objeto Java a partir de un fichero XML que lo describe. El el ejemplo cremaos un inflater para layouts.

En esta clase el método más importante es getView(), que usa el sistema para pedir cada uno de los elementos a insertar. Cuando se llame a getView(), nos indicarán tres parámetros: la posición del elemento a insertar, una vista reciclada y el layout contenedor donde se insertará el elemento. Este método ha de devolver una vista con la información adecuada del elemento a insertar.

El parámetro vistaReciclada se utiliza para mejorar el rendimiento en la creación de vistas. Para la primera llamada a getView(), este parámetro será nulo y tendremos que crear una nueva vista e inflarla con el inflater desde un XML (este proceso puede ser algo lento). Pero para las siguientes llamadas, este parámetro contendrá la vista devuelta por nosotros en la llamada anterior, para esta posición. De esta forma ya no será necesario crearla desde cero y solo tendremos que modificar sus características y devolverla. El resto del método se utiliza para actualizar cada campo, según el lugar a representar.

Nota:En este ejemplo el  BaseAdapter  devuelve siempre el mismo tipo de vista. Aunque esta es la forma más habitual de trabajar, no tiene por qué ser necesariamente así. Cada elemento devuelto desde getView() podría ser una vista totalmente diferente a las demás.

Finalmente tenemos que definir tres métodos que permiten acceder a la información representada. El método getCount() será el que indique cuántos elementos queremos mostrar. El método getItem() devolverá el objeto libro que se muestra en una determinada posición. El método getItemId() devolverá el id que se muestra en una determinada posición. El id es un valor numérico que identifica cada elemento. Si utilizamos un vector se suele utilizar el índice del vector como id, mientras que en bases de datos suele ser el campo de indexación principal. En esta primera versión del adaptador se muestran todos los lugares en el mismo orden en que los tenemos en el vector, por lo tanto, el id coincide con la posición. Más adelante mostraremos un ejemplo más complejo donde ya no ocurrirá esto.

2. Reemplaza en la clase onCreate() de la clase MainActivityla inicialización de adaptadorpor:

adaptador = new AdaptadorLugares(this);

3. Ejecuta la aplicacióny verifica el resultado.

NOTA: En algunos casos el adaptador ha de trabajar con listas muy grandes o estas listas han de ser creadas desde un servidor. En estos casos es mejor ir solicitando la información a medida que se va representando. Un ejemplo se muestra en la aplicación ApiDemos descrita en el capítulo 1, en la actividad: com.example.android.apis.view.List13

Detectar una pulsación sobre un elemento de la lista

Un ListView puede tener diferentes componentes que nos permitan interaccionar con el usuario. Por ejemplo, cada elemento definido en getView() puede tener botones para diferentes acciones.

Hay un tipo de interacción muy sencilla de definir. La clase ListActivity tiene un método que es invocado cada vez que se pulsa sobre un elemento de la lista. El siguiente ejercicio ilustra cómo utilizarlo.

Ejercicio paso a paso: Detectar una pulsación sobre un elemento de la lista

1. Haz que la clase MainActivity clase implemete el interfaz OnItemClickListener  añadiendo el código subrayado:

public class MainActivity extends ActionBarActivity
                          implements OnItemClickListener {
   …
   @Override
   protected void onCreate(Bundle savedInstanceState) {
      …
     
      listView.setOnItemClickListener(this);
   }
   …

En este caso vamos a definir un escuchador de eventos. Los eventos son generados por el listView cuando se pulsa sobre el, y son escuchados por nuestra clase (this). Para conseguir esto, nuestra clase ha de implementar el interfaz OnListItemClick. Y al listView le indicamos que nos mande  a nosotros los eventos mediante setOnListItemClick(this)(this).

2. Añade el siguiente método  a la clase:

@Override
protected void onItemClick(AdapterView parent,View vista,
                                            int posicion, long id){
   Intent i = new Intent(this, VistaLugar.class);
   i.putExtra("id", id);
   startActivity(i);
}

El método onItemClick() dispone de cuatro parámetros: el adaptador que se ha llamado, la vista pulsada dentro de este listView, la posición del elemento pulsado y su id.

3. Ejecuta la aplicación, pulsa sobre un lugar para ver su de información detallada.

[1] Para saber más sobre clases genéricas puedes ver el siguiente Polimedia http://youtu.be/N3yy2pfUaE0.