T#005 – ListView: creare liste di elementi nelle applicazioni Android

Nelle applicazioni mobile il componente grafico sicuramente più usato è la lista di oggetti. Esempi che includono questo componente li trovate praticamente in tutte le applicazioni: la lista dei contatti, delle ultime chiamate effettuate, le applicazioni sul market, gli sms inviati e ricevuti. In questo tutorial vedremo come creare liste di elementi per le nostre applicazioni Android, partendo dalle più semplici e arrivando, man mano, a quelle più complesse.

I dati mostrati negli esempi prenderanno in considerazione l’elenco delle province italiane organizzate in un array di oggetti Provincia:

1
2
3
4
5
6
7
public static final Provincia[] DB = new Provincia[]
	{
		new Provincia("Agrigento", "AG", "Sicilia"), 
		new Provincia("Alessandria", "AL", "Piemonte"), 
		//....
		new Provincia("Viterbo", "VT", "Lazio") 
	};

Il componente di Android da utilizzare per visualizzare una lista è ListView. Questo componente si occupa della parte grafica, i dati mostrati e il layout di una singola cella vengono presi da un ListAdapter. Una ListView ha sempre associato un ListAdapter, questa associazione viene fatta chiamando il metodo setAdapter.
ListAdapter è un’interfaccia Java, solitamente non si implementa direttamente ma si usa, a seconda del contesto, una delle seguenti classi:

  • ArrayAdapter: gestisce un array o da una lista di oggetti e mostra il toString degli oggetti in un TextView;
  • SimpleAdapter: permette di creare viste complesse (per esempio contenenti checkbox, immagini e testo) partendo da dati fissi;
  • CursorAdapter: gestisce un Cursor, da utilizzare per mostrare dati presi da un database;

Il modo più semplice per mostrare una lista è creare una Activity che estenda ListActivity ed impostare l’adapter da usare:

1
2
3
4
5
6
7
8
9
10
11
public class MainActivity extends ListActivity
{
	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setListAdapter(new ArrayAdapter<Provincia>(this,
			android.R.layout.simple_list_item_1, 
			android.R.id.text1, Provincia.DB));
	}
}

I parametri passati al costruttore di ArrayAdaper indicano:

  • Context: tutti gli adaper hanno bisogno di un oggetto Context (solitamente rappresentato dall’Activity);
  • layout da usare per una cella: in questo caso usiamo un layout di Android che contiene un singolo campo di testo;
  • id del TextView da popolare con il risultato del metodo toString del singolo oggetto;
  • array degli oggetti da usare per popolare la lista.

Il risultato visto sull’emulatore è questo:


ArrayAdapter

Vediamo adesso come utilizzare un SimpleAdapter. Usando questa classe è possibile usare un layout più complesso contenente più di una View. Per questo esempio useremo il layout di Android android.R.layout.simple_list_item_2 che contiene due TextView. I primi tre parametri passati al costruttore di SimpleAdapter indicano:

  • Context da usare
  • i dati da mostrare organizzati in una lista di mappe chiave-valore
  • il layout da usare per mostrare una singola riga

Il quarto e quinto parametro sono sue array della stessa lunghezza che indicano come mappare gli oggetti della lista dei dati alle view; il primo array di stringhe indica le chiavi nella map dei dati di un oggetto mentre il secondo array di int indica le corrispondenti View contenute nel layout in cui mostrare i dati.

Il metodo onCreate della Activity si complica un po’ e diventa:

1
2
3
4
5
6
7
8
9
@Override
public void onCreate(Bundle savedInstanceState)
{
	super.onCreate(savedInstanceState);
	setListAdapter(new SimpleAdapter(this, Provincia.getData(), 
		android.R.layout.simple_list_item_2, new String[]
	{ Provincia.NOME, Provincia.REGIONE }, new int[]
	{ android.R.id.text1, android.R.id.text2 }));
}

Il risultato finale è questo:


ArrayAdapter

Fino ad adesso abbiamo sempre usato layout standard di Android, ovviamente questo va bene solo per cose molto semplici.

Definiamo un layout custom contenuto nel file row.xml contenente tre TextView:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical" android:layout_width="fill_parent"
	android:layout_height="fill_parent">
 
	<TextView android:layout_width="fill_parent" android:text="Provincia"
		android:layout_height="wrap_content" android:id="@+id/provincia"
		android:textSize="22sp" />
	<TextView android:layout_width="fill_parent" android:text="Regione"
		android:layout_height="wrap_content" android:id="@+id/regione"
		android:textSize="14sp" android:layout_below="@id/provincia"
		android:layout_alignParentBottom="true" />
	<TextView android:layout_width="wrap_content" android:text="Co"
		android:layout_height="wrap_content" android:id="@+id/codice"
		android:textSize="16sp" android:layout_alignParentRight="true"
		android:layout_centerVertical="true" />
</RelativeLayout>

Per questo esempio vogliamo mappare i campi provincia, regione e codice nei rispettivi TextView (questo poteva essere fatto anche con un SimpleAdapter) ma vogliamo anche applicare una formattazione specifica al testo in base ai dati. Creiamo una classe che estende un BaseAdapter e riscrive i metodi abstract, il metodo che contiene il mapping fra i dati e la vista è getView:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public void onCreate(Bundle savedInstanceState)
{
	super.onCreate(savedInstanceState);
	setListAdapter(new MyAdapter());
}
 
private final class MyAdapter extends BaseAdapter
{
	@Override
	public View getView(int position, View convertView, ViewGroup parent)
	{
		if (convertView == null)
		{
			convertView = getLayoutInflater().inflate(
				R.layout.row, null);
		}
		Provincia provincia = (Provincia) getItem(position);
 
		TextView provinciaTextView = (TextView) 
			convertView.findViewById(R.id.provincia);
		TextView codiceTextView = (TextView) 
			convertView.findViewById(R.id.codice);
		TextView regioneTextView = (TextView) 
			convertView.findViewById(R.id.regione);
 
		codiceTextView.setText(provincia.getCodice());
		provinciaTextView.setText(provincia.getNome());
		regioneTextView.setText(provincia.getRegione());
		if (provincia.isRegioneStatutoSpeciale())
		{
			regioneTextView.setTypeface(Typeface.DEFAULT, 
				Typeface.ITALIC);
		}
		else
		{
			regioneTextView.setTypeface(Typeface.DEFAULT, 
				Typeface.NORMAL);
		}
		return convertView;
	}
 
	@Override
	public long getItemId(int position)
	{
		return position;
	}
 
	@Override
	public Object getItem(int position)
	{
		return Provincia.DB[position];
	}
 
	@Override
	public int getCount()
	{
		return Provincia.DB.length;
	}
}

Il metodo getView è quello che contiene la logica di popolamento di una singola riga della lista, prende in input tre parametri:

  • position: posizione dell’elemento da mostrare nella lista dei dati
  • convertView: View già istanziata da riutilizzare per evitare spreco di risorse
  • parent: View di riferimento, in questo caso viene passata la ListView

Il componente ListView esegue un riuso automatico delle View utilizzate per ogni riga per poter minimizzare l’occupazione di memoria; il secondo parametro passato al metodo contiene la View già istanziata da riutilizzare. Questo riuso viene fatto per avere lo scroll di una lista più fluida e deve essere sfruttato, nell’esempio fatto il primo if all’inizio del metodo serve a questo: esegue l’inflate di una nuova View solo se il secondo parametro del metodo è null.
Il resto del metodo si occupa di popolare i vari TextView in base ai valori dei campi dell’oggetto da mostrare; l’ultimo if, infine, imposta una formattazione custom (in questo caso il font italic) nel caso in cui la regione sia a statuto speciale.

L’activity in esecuzione è questa:


ArrayAdapter

Non utilizzare la convertView passata al metodo getView, ma ricreare tutte le volte la view è un errore da evitare, nel caso di view complesse se ancora lo scroll della lista non è fluido può essere usato un altro accorgimento. In pratica le chiamate al metodo findViewById sono la parte più dispendiosa nel metodo visto, vediamo come evitarle.

Creiamo una nuova classe che si occupa di effettuare le chiamate a findViewById e memorizza le varie istanze dei TextView usati nel layout:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
private static class RowWrapper
{
	private TextView provinciaTextView;
 
	private TextView codiceTextView;
 
	private TextView regioneTextView;
 
	public RowWrapper(View convertView)
	{
		provinciaTextView = (TextView) 
			convertView.findViewById(R.id.provincia);
		codiceTextView = (TextView) 
			convertView.findViewById(R.id.codice);
		regioneTextView = (TextView) 
			convertView.findViewById(R.id.regione);
	}
 
	public void poulate(Provincia provincia)
	{
		codiceTextView.setText(provincia.getCodice());
		provinciaTextView.setText(provincia.getNome());
		regioneTextView.setText(provincia.getRegione());
		if (provincia.isRegioneStatutoSpeciale())
		{
			regioneTextView.setTypeface(Typeface.DEFAULT, 
				Typeface.ITALIC);
		}
		else
		{
			regioneTextView.setTypeface(Typeface.DEFAULT, 
				Typeface.NORMAL);
		}
	}
}

Il metodo populate effettua il popolamento dei campi di testo in base all’oggetto passato. L’oggetto RowWrapper viene memorizzato nel campo tag della View in modo da effettuare le chiamate al metodo findViewById solo quando una View viene creata. La nuova versione del metodo getView è molto semplice:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public View getView(int position, View convertView, ViewGroup parent)
{
	RowWrapper wrapper;
	if (convertView == null)
	{
		convertView = getLayoutInflater().inflate(
			R.layout.row, null);
		wrapper = new RowWrapper(convertView);
		convertView.setTag(wrapper);
	}
	else
	{
		wrapper = (RowWrapper) convertView.getTag();
	}
	Provincia provincia = (Provincia) getItem(position);
	wrapper.poulate(provincia);
 
	return convertView;
}

Per provare a usare una ListView sono disponibili i sorgenti del progetto da importare in Eclipse.

By Fabio Collini

Da agosto 2009 sono un freelance android developer, ho rilasciato due applicazioni nell'Android Market: Apps Organizer e Folder Organizer. Presso OmniaGroup ricopro il ruolo di Tech Leader nell'ambito di un progetto di rich internet application che utilizza JSF, JPA(EclipseLink) ed EJB3.

9 comments

  1. ciao, scusa ma non riesco a capire il metodo getData di Provincia cosa torna

  2. Ciao, ritorna una List di Map. Ogni riga in pratica è una map in cui la chiave è il nome del campo e il valore è quello da mostrare nella ListView. E’ un po incartato ma il SimpleAdapter funziona così…
    Fabio

  3. potrei far vedere il mio progetto molto simile a questo perchè in fase in prova sul terminare virtuale non mi funziona !

  4. Ciao,scusami volevo chiederti…io come posso creare una lista simile a questa,ma fare in modo che ad ogni click dell array(Es. click firenze=pagina web) corrisponda ad una pagina web?E quindi apre il browser..?
    Attendo risposta
    Alfonso

    1. Ciao, visto che l’Activity di questo esempio estende ListActivity ti basta riscrivere il metodo onListItemClick. In questo metodo puoi richiamare startActivity passando l’Intent per aprire una pagina web
      Ciao, Fabio

  5. Ma perchè se provo ad incollare la roba della classe myadapter mi da una quantità di errori infinita??

  6. Complimenti per la guida!
    Ho un dubbio:
    Perchè la classe RowWrapper viene definita static se di fatto (da quel che ho capito) viene salvato l’oggetto wrapper con la setTag?
    (di fatti ho provato a togliere la parola static e il programma funziona correttamente)
    Grazie

Comments are closed.