Todo sobre las ListViews, ViewHolder y CacheHolder

Muchas ocasiones en el desarrollo de aplicaciones Android debemos usar una Lista de elementos, en Android esto corresponde al elemento listview, nos permite añadir elementos solamente teniendo una lista de estos. El modo por defecto que usamos para crear listview, es costoso y en listas muy largas el dispositivo puede llegar a ralentizarse notablemente para esto optimizaremos el listview con ViewHolder y cache.

A modo de resumen podemos crear una listview haciendo un extends de ListActivity (http://developer.android.com/guide/tutorials/views/hello-listview.html)

Que lo podemos crear simplemente con 3 lineas de código en el método onCreate:

 

        //Create new Adapter from the String Array Global.countries
        ArrayAdapter<String> countriesAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, Global.COUNTRIES);
        //Set Adapter to ListActivity
        setListAdapter(countriesAdapter);
        getListView().setTextFilterEnabled(true);

Más abajo podréis descargados el código final con todos los ejemplos, también he puesto un sencillo ejemplo de como añadir un listener del item

En cambio si queremos crear nosotros una lista de elementos con el ArrayAdapter necesitamos construir uno nuevo de la siguiente manera:

 

public class ArraySimple extends ArrayAdapter{
	Context ctx = null;
	
	public ArraySimple(Context context) {
		super(context, R.layout.simple_item , Global.COUNTRIES);
		ctx = context;
	}

	public View getView(int pos, View convertView, ViewGroup parent){
		
		View item = convertView;
		
    	 LayoutInflater mInflater = LayoutInflater.from(ctx);
    	 item = mInflater.inflate( R.layout.simple_item, null);
    	 TextView simpleText = (TextView) item.findViewById(R.id.text);
    	 
        String country = Global.COUNTRIES[pos];
        
        simpleText.setText(country);
        
        return item;
	}
} 

Como vemos necesitamos el context de la aplicación y un layout para poner la estructura de cada row, en este caso simplemente es:

 




    


Estos dos casos es para hacer una simple listview uno usando el del sistema y el segundo personalizando con nuestro propio layout. En el mundo de Android se consideran buenas practicas construir nuestros propios ArrayAdapter con ViewHolder, ademas de la consiguiente mejora.

El primer método es usando el ViewHolder para layouts propios que solo contengan texto. El segundo usaremos la Cache para usar eficientemente layouts con imágenes.

1. ViewHolder consiste en guardar las variables que nos devuelve el findview de nuestro layout, que se ejecuta en cada fila y es costoso de resolver. Sobretodo cuando Android no guarda los elementos de la filas y siempre tiene que ir creando y borrando cuando los objetos aparezcan y desaparezcan. Para evitar esto crearemos una clase que guardara estas clases la primera vez y luego las recupera si ya están creadas previamente.

Crearemos nuestro ArrayAdapter con una pequeña diferencia respecto al anterior:

 

public class ArrayHolder extends ArrayAdapter{
	Context ctx = null;
	
	public ArrayHolder(Context context) {
		super(context, R.layout.simple_item , Global.COUNTRIES);
		ctx = context;
	}

	public View getView(int pos, View convertView, ViewGroup parent){
		
		ViewHolder holder = null;
		View item = convertView;
		
        if (item == null || !( item.getTag() instanceof ViewHolder)) {
        	
        	 LayoutInflater mInflater = LayoutInflater.from(ctx);
        	 item = mInflater.inflate( R.layout.simple_item, null);
             // Creates a ViewHolder and store references to the two children views we want to bind data to.
             holder = new ViewHolder();
             holder.simpleText = (TextView) item.findViewById(R.id.text);
             //Save holder
             item.setTag(holder);
        }else{
            // Get the ViewHolder back 
            holder = (ViewHolder) item.getTag();
        }
        
        String country = Global.COUNTRIES[pos];
        
        holder.simpleText.setText(country);
        
        return item;
	}
	
	class ViewHolder{
		TextView simpleText;
	}
}

2. Cache sigue la misma idea de la lista anterior pero en este caso cuando el layout tengan imágenes podremos guardar el drawable que usaremos en todas las filas del listview, ahorrando tener que cogerlo cada vez de los recursos.

Para conseguirlo añadiremos la clase Cache que permitirá guardar el drawable como antes hacíamos con el ViewHolder.

 

public class ArrayCache extends ArrayAdapter{
	Context ctx = null;
	
	public ArrayCache(Context context) {
		super(context, R.layout.add_item , Global.COUNTRIES);
		ctx = context;
	}

	public View getView(int pos, View convertView, ViewGroup parent){
		
		ViewHolder holder = null;
		View item = convertView;
		
        if (item == null || !(item.getTag() instanceof ViewHolder)) {
        	
        	 LayoutInflater mInflater = LayoutInflater.from(ctx);
        	 item = mInflater.inflate( R.layout.add_item, null);
             // Creates a ViewHolder and store references to the two children views we want to bind data to.
             holder = new ViewHolder();
             holder.image = (ImageView) item.findViewById(R.id.image);
             holder.simpleText = (TextView) item.findViewById(R.id.text);
             //Save holder
             item.setTag(holder);
        }else{
            // Get the ViewHolder back to get fast access to the TextView
            // and the ImageView.
            holder = (ViewHolder) item.getTag();
        }
        
        String country = Global.COUNTRIES[pos];
        
        holder.simpleText.setText(country);
        
        //Save image to cache
        if (Cache.add == null){
            Cache.add = ctx.getResources().getDrawable(R.drawable.plus);
        }
        holder.image.setBackgroundDrawable(Cache.add);
        return item;
	}
	
	class ViewHolder{
		TextView simpleText;
		ImageView image;
	}
	static class Cache{
		static Drawable add = null;
	}
}

Para ver el código podéis descargarlo de github: Android_lists_ViewHolder_cache_Example

Para ver las páginas que me han inspirado a escribir este post:
RocBoronat: Interesante método para calcular el rendimiento entre una o otra implementación
android-viewholder-banana-example : De RocBoronat donde implementa lo los viewholders y cache
Sgolivert: Explica con gran detalle como implementar el ViewHolder

3 thoughts on “Todo sobre las ListViews, ViewHolder y CacheHolder”

  1. Hola. Me estoy iniciando en programación java-android y este tutorial me parece excelente. Lo he probado en el emulador y funciona a la perfección ¿pero como puedo hacer para que también funcione el filtro con el teclado de la tablet?. En la tablet no aparece el teclado cuando el listview tiene el enfoque.
    Muchas gracias…
    Saludos…

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos necesarios están marcados *