Si sigues leyendo podrás aprender sobre:
La clase ListView
Crear una actividad basada en una lista
Diseño en Android Studio
Añadir datos desde un adaptador hacia tu lista
Interacción ListView-Adapter
La clase ArrayAdapter
Crear tu propio origen de datos
Sobrescribir la clase ArrayAdapter
Poblando una lista
Usar un ImageView en los elementos de la lista
Actualizar un ListView
Manejar eventos con OnItemClickListener
Personalizar el Selector de una Lista
¿Qué es un StateListDrawable?
Crear un Selector para un ListView
Descargar A continuación veremos un bosquejo del diseño:
Si ejecutamos nuestra aplicación con la anterior definición, observaríamos el siguiente resultado:
¿Qué sucedió?, ¿Por qué no están las filas que se mostraban en la previsualización?
La respuesta a esa inquietud esta en la siguiente sección...
Para implementar un ArrayAdapter en una lista simplemente seguimos los siguientes pasos:
Paso 1
Declarar las instancias de la lista y el adaptador:
ListView lista;
ArrayAdapter<String> adaptador;
...
Al declarar el adaptador describimos el tipo de objetos que manejará internamente, el anterior ejemplo tiene el tipo String.
Paso 2
Obtener programáticamente una referencia de la lista:
lista = (ListView)findViewById(R.id.lista);
Paso 3
Inicializar el adaptador:
adaptador = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1, arregloCadenas);
Donde los parámetros del constructor tienen el siguiente propósito:
Context context: Representa el contexto de la aplicación. Usamos this para indicar que será la actividad que se basa en la creación de la lista.
int resource: Es el recurso de diseño o layout que representará cada fila de la lista. En este caso usamos un recurso del sistema llamado simple_list_item_1.xml. Este layout contiene un solo TextView que contendrá el texto de cada fila.
T[] objects: Es la referencia del array de objetos de tipo T con los cuales crearemos la lista. Si deseas puedes variar el constructor y usar una lista dinámica List<T> en lugar del array.Paso 4
Y finalmente relacionar el adaptador con la lista:
lista.setAdapter(adaptador);
Como ves, usamos un método set() para asignar el adaptador al atributo de la lista. Esto significa que desde el punto de vista de programación orientada a objetos, la clase ListView se compone de un objeto Adapter.
Pero aun falta un factor que resolver. ArrayAdapter dentro de su método getView() llama al método toString() de cada objeto de nuestro origen de datos. Si dejásemos nuestra clase Tarea con el anterior aspecto, heredaría el método de la clase Object y tendríamos una serie de nombres muy raros, estilo "Tarea@123422" y eso no es lo que queremos.
Lo que se necesita es decirle al adaptador que vamos a enviar dos cadenas (nombre y hora) para que se ubiquen en el View que se inflará. Y la única manera de realizar esto es sobrescribiendo el método getView().
Cabe destacar que estamos probando un ejemplo estático, en algunos casos la cantidad de elementos de la lista cambia en tiempo real, por lo que debemos ajustar el código para ese tipo de situaciones. También es importante resaltar que podemos crear cualquier tipo de View para las filas, la cuestión está en extender la clase ArrayAdapter para que se acople a tus necesidades.
Esta vez ya no podemos apoyarnos en los recursos de Android , ya que no existe un layout previamente diseñado que permita insertar una imagen en un elemento. Por esta razón crearemos nuestro propio archivo de diseño.
Para ello crea un nuevo recurso llamado image_list_item con un nodo raíz RelativeLayout e implementa las posiciones que vimos en el boceto anterior.
La descripción quedaría mas o menos así:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="wrap_content"
android:padding="5dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/category"
android:layout_marginRight="10dp"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="New Text"
android:id="@+id/text1"
android:layout_alignParentTop="true"
android:layout_toRightOf="@+id/category"
android:textAppearance="@android:style/TextAppearance.Holo.Large"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="New Text"
android:id="@+id/text2"
android:layout_below="@+id/text1"
android:layout_toRightOf="@+id/category"
android:layout_alignParentBottom="true" />
</RelativeLayout>
Usaremos los siguientes tres iconos para representar las siguientes categorías: Salud (ic_health), Finanzas (ic_money) y Carrera (ic_carreer):
Todos ellos fueron descargados de un excelente portal llamado IconFinder. Elegí un tamaño de 48x48 ya que usaré un emulador mdpi.
Ahora, el siguiente paso es asignar una categoría a cada tarea. Para ello crearemos un nuevo atributo llamado categoría en nuestra clase Tarea. En él asignaremos la referencia del icono correspondiente a la categoría.
Así que la clase Tarea quedaría de la siguiente forma:
package TUPAQUETE.todo;
public class Tarea{
private String nombre;
private String hora;
private int categoria;
public Tarea(String nombre, String hora, int categoria){
this.nombre = nombre;
this.hora = hora;
this.categoria = categoria;
}
public void setNombre(String nombre){
this.nombre = nombre;
}
public void setHora(String hora){
this.hora = hora;
}
public void setCategoria(int categoria){
this.categoria=categoria;
}
public String getNombre(){return nombre;}
public String getHora(){return hora;}
public int getCategoria(){return categoria;}
}
Seguidamente, actualizaremos el método getView() del adaptador. Básicamente solo debemos agregar las instrucciones para obtener la instancia del ImageView y asignarle el drawable correspondiente con setImageResource():
package TODO.todo;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;
public class TareaArrayAdapter extends ArrayAdapter<Tarea> {
public TareaArrayAdapter(Context context, List<Tarea> objects) {
super(context, 0, objects);
}
@Override
public View getView(int position, View convertView, ViewGroup parent){
//Obteniendo una instancia del inflater
LayoutInflater inflater = (LayoutInflater)getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
//Salvando la referencia del View de la fila
View listItemView = convertView;
//Comprobando si el View no existe
if (null == convertView) {
//Si no existe, entonces inflarlo con image_list_view.xml
listItemView = inflater.inflate(
R.layout.image_list_item,
parent,
false);
}
//Obteniendo instancias de los elementos
TextView titulo = (TextView)listItemView.findViewById(R.id.text1);
TextView subtitulo = (TextView)listItemView.findViewById(R.id.text2);
ImageView categoria = (ImageView)listItemView.findViewById(R.id.category);
//Obteniendo instancia de la Tarea en la posición actual
Tarea item = getItem(position);
titulo.setText(item.getNombre());
subtitulo.setText(item.getHora());
categoria.setImageResource(item.getCategoria());
//Devolver al ListView la fila creada
return listItemView;
}
}
Ahora solo queda por cambiar la inicialización estática de la fuente de información. Asignaremos los ids de cada drawable correspondiente:
static{
TAREAS.add(new Tarea("Trotar 30 minutos","08:00",R.drawable.ic_health));
TAREAS.add(new Tarea("Estudiar análisis técnico","10:00",R.drawable.ic_money));
TAREAS.add(new Tarea("Comer 4 rebanadas de manzana","10:30",R.drawable.ic_health));
TAREAS.add(new Tarea("Asistir al taller de programación gráfica","15:45",R.drawable.ic_carreer));
TAREAS.add(new Tarea("Consignarle a Marta","18:00",R.drawable.ic_money));
}
Al ejecutar el proyecto verías lo siguiente:
añadir un Action Button a la Action Bar llamado "Limpiar", el cual tendrá como fin limpiar todos los elementos de la lista a través del método clear() del adaptador. Implementemos estas instrucciones en onOptionsItemSelected():
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_clear) {
//Limpiar todos los elementos
adaptador.clear();
return true;
}
return super.onOptionsItemSelected(item);
}
Al probar veremos que automáticamente los elementos de la lista han sido erradicados:
Nota: Un elemento Toast es una pequeña ventana emergente con una duración determinada, el cual despliega un mensaje para notificar al usuario el cumplimiento de alguna acción. Su método makeText() genera una instancia prefabricada, la cual posee un solo TextView, esto hace que no se requier