Livello di difficoltà: facileVersione SDK utilizzata: 1.6Link al file compresso del progetto eclipse: file in allegatoPremetto di non essere un esperto riguardo alla programmazione su Android, e se scriverò qualche incorrettezza questo tutorial sarà un'occasione di crescita prima di tutto per me

.
Per rendere più pulita e ordinata la visualizzazione delle liste in android è molto elegante l'uso degli header, in pratica si tratta di suddividere i dati della lista in gruppi omogeni rispetto ad una caratteristica comune ed inserire un titolo per ogni gruppo (come i contatti suddivisi per lettera in ordine alfabetico).
Eseguendo ricerche sull'implementazione di tale funzioni energono due strade possibili :
- Utilizzo di diversi adapter : praticamente si tratta di separare l'adapter che gestisce i dati della lista in più adapter già suddivisi in gruppi omogenei e utilizzare il metodo setHeader() delle ListView prima di aggiungere ogni adapter alla lista.
Questa soluzione non mi è sembrata molto pratica, sopratutto nel caso in cui si volesse utilizzare un CursorAdapter. - Utilizzare un layout custom per gli item : in questa soluzione si crea un layout custom per gli item della lista inserendo una TextView che conterrà il titolo. Tale TextView è di default invisibile e verrà visualizzata solo dove necessario.
Questo tutorial si preffigge di illustrare un'implementazione della seconda opzione.
In pratica si vuole ottenere un'ipotetica lista della spesa in cui i vari oggetti da comperare siano divisi per tipo :
Sorgenti:1) Creazione Custom List Item simple_list_item.xml Per prima cosa dobbiamo realizzare un custom layout che conterrà i dati da visualizzare nella lista e l'header nascosto :
...
<TextView android:id="@+id/simple_header" android:textStyle="bold"
android:textColor="#000" android:textColorHighlight="#000"
android:background="#DDD" android:layout_centerInParent="true"
android:visibility="gone" android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<TextView android:id="@+id/list_item_name" style="@style/listItemTitle"
android:text="name" android:layout_width="wrap_content"
android:layout_height="wrap_content" />
...Il layout contiene due TextView :
- simple_header : conterrà il titolo del gruppo e sarà visibilile solo dove necessario, si noti android:visibility="gone" che non solo nasconde la TextView, ma non le fa occupare spazio nel layout.
- list_item_name : conterrà il nome dell'oggetto e sarà sempre visibile.
2) Creazione del databaseIn questo tutorial si utilizzerà un database gestito dalla classe
it.pinabello.android.tutorial.SimpleHeaderTutorialDB che si occupa di creare una tabella
SHOP_LIST per contenere i dati della lista.
Struttura tabella SHOP_LIST| Nome campo | Descrizione |
| _id | Id autoincrementale |
| NAME | nome dell'oggetto |
| TYPE | categoria dell'item |
Esempio di dati| NAME | TYPE |
| Carote | V |
| Rucola | V |
| Arance | F |
| Mandarini | F |
| Banane | F |
| Sapone | C |
| Stracci | C |
| Pane | A |
Quindi quello che si vuole ottenere è suddividere la lista per valori omogenei del campo
TYPE4) Creazione Custom Adapter SimpleHeadersCursorAdapterPer modificare il comportamento standard della classe ListView è necessario utilizzare un Custom Adapter, visto che questo tutorial utilizza un cursore per ottenere i dati la nostra classe adapter estende SimpleCursorAdapter.
L'unico metodo che è necessario sovrascrivere è getView(...) dove viene implementata la logica di visualizzazione dei titoli.
...
/*
* @param headers : hashmap che contiene come chiavi i possibili valori del campo da usare per
* la suddivisione (A,C,F,V, per questo tutorial) e come value la descrizione da
* visualizzare nell'header.
* @param fieldName : nome del campo presente nel cursore da usare per la suddivisione
* (TYPE per questo tutorial)
* @param headerId : id della TextView del custom layout utilizzato per la lista
* (R.id.simple_header per questo tutorial)
*/
public SimpleHeadersCursorAdapter(Context arg0, int arg1, Cursor cursor, String[] arg3, int[] arg4,
HashMap<String, String> headers, String fieldName, int headerId) {
super(arg0, arg1, cursor, arg3, arg4);
this.headers = headers;
this.cursor = cursor;
this.fieldIndex = cursor.getColumnIndex(fieldName); // Ottengo l'indice del campo del cursore
this.headerId = headerId;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
cursor.moveToPosition(position);
/*
* L'hashmap headersIndex viene valorizzato con la coppia
* chiave/valore "tipo/posizione header da visualizzare"
* se il tipo non è presente come chiave nell'hashmap significa che stiamo incontrando il primo
* oggetto di questa categoria e che a questa posizione è necessario rendere visibile il titolo.
* Per tutti gli altri oggetti della stessa categoria la condizione sarà false e il titolo non verrà
* visualizzato.
* Perchè questa logica funzioni correttamente il cursore deve essere ordinato per categoria.
*/
if (!headersIndex.containsKey(cursor.getString(fieldIndex))) {
headersIndex.put(cursor.getString(fieldIndex), position);
}
View view = super.getView(position, convertView, parent);
// Ottengo la TestView del titolo
TextView textView = (TextView) view.findViewById(headerId);
/*
* Se questa posizione è contenuta nell'HashMap headersIndex il titolo deve essere visibile
*/
if (headersIndex.containsValue(position)) {
// Il titolo viene reso visibile
textView.setVisibility(View.VISIBLE);
// Viene reperita la descrizione del titolo e settata nella TextView
textView.setText(headers.get(cursor.getString(fieldIndex)));
} else {
// Il titolo viene reso nascosto
textView.setVisibility(View.GONE);
// Reset del titolo
textView.setText("");
}
return view;
}
/*
* Il metodo refreshHeaders va richiamato nel caso in cui vengano eliminati o aggiunti oggetti
* alla lista, in quanto svuotanto l'hashMap si richiede un nuovo calcolo della posizione dei titoli
*/
public void refreshHeaders() {
headersIndex = new HashMap<String, Integer>();
}
...Come si può vedere dal codice nel metodo getView(...) viene creato un'HashMap che contiene le posizioni in cui visualizzare i titoli e successivamente viene ottenuta la View corrente dove, se la posizione della View è presente nell'HashMap, il titolo viene reso visibile.
4) Creazione ListActivity SimpleListActivityLa classe SimpleListActivity si occupa di utilizzare tutte le componenti discusse e visualizzare la lista.
...
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Reperimento riferimento al database.
SimpleHeaderTutorialDB simpleHeaderTutorialDB = new SimpleHeaderTutorialDB(this);
SQLiteDatabase database = simpleHeaderTutorialDB.getReadableDatabase();
/*
* Esecuzione query si noti il valore "Spesa.TYPE + ", " + Spesa.NAME" che esegue l'ordinamento
* del cursore prima per tipo e poi per nome
*/
Cursor cursor = database.query(Spesa.TABLE_NAME, new String[] {
Spesa.ID, Spesa.NAME, Spesa.TYPE }, null, null, null, null,
Spesa.TYPE + ", " + Spesa.NAME);
startManagingCursor(cursor);
/*
* HashMap che contiene le coppie tipo/descrizione, per tali valori sarebbe più corretto utilizzare
* il file values/array.xml, per semplicità in questo tutorial viene gestito quì.
*/
HashMap<String, String> hash = new HashMap<String, String>();
hash.put("V", "Verdure");
hash.put("F", "Frutta");
hash.put("A", "Altro");
hash.put("V", "Verdure");
hash.put("C", "Casalinghi");
/*
* Creazione SimpleHeadersCursorAdapter si noti in particolare :
* R.layout.simple_list_item : custom item layout
* hash : HashMap tipo /descrizione appena creato
* Spesa.TYPE : nome campo del cursore che contiene la categoria
* R.id.simple_header : id del TextView che contiene il titolo
*/
CursorAdapter adapter = new SimpleHeadersCursorAdapter(this,
R.layout.simple_list_item, cursor, new String[] { Spesa.NAME },
new int[] { R.id.list_item_name }, hash, Spesa.TYPE,
R.id.simple_header);
setListAdapter(adapter);
}
...