Guida agli Adapter e le ListView in Android

di  Antonio Coschignano, sabato 06 ottobre 2012

Uno dei controlli grafici fondamentale e più utilizzato nella piattaforma Android è certamente la ListView. Essa viene utilizzata per rappresentare liste, elenchi attraverso un adapter che viene implementato all'interno di essa. L'adapter di cui stiamo parlando è un componente che si occupa della rappresentazione grafica dei dati e dell'interazione con essi, per ogni elemento della ListView. In questo tutorial esamineremo i vari adapter già definiti nell'ambiente Android, ma anche quelli che possiamo implementare secondo le nostre esigenze dando uno sguardo anche all'ottimizzazione di essi. Infine spiegheremo come gestire i listeners della ListView.

Adesso partiamo innanzitutto nel definire una ListView nel nostro progetto, all'interno di un layout che chiameremo main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
...
<ListView
    android:id="@+id/listViewDemo"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" >
</ListView>
...
</LinearLayout>

La ListView visualizza le View ottenute da una implementazione dell'interfaccia ListAdapter che gli viene passata attraverso il metodo:

public void setAdapter(ListAdapter adapter)

L'interfaccia ListAdapter implementa a sua volta l'interfaccia Adapter che insieme descrivono la modalità di visualizzazione dell'intera lista di elementi. Esistono diverse implementazioni di questo modello, ma quello che vedremo in quest'articolo è l'ArrayAdapter.

ArrayAdapter

L'ArrayAdapter è un'implementazione dell'interfaccia ListAdapter che viene utilizzata per visualizzare un'array di elementi dove ad ogni elemento della lista è associata una TextView. Nella TextView viene visualizzato il toString() dell'elemento. L'ArrayAdapter implementa diversi costruttori da utilizzare in base alle nostre esigenze:

  • public ArrayAdapter(Context context, int idTextView, T [] object): Questo costruttore si aspetta oltre al context ed all'array, anche l'id della TextView, dove visualizzare ciascun elemento.
  • public ArrayAdapter(Context context, int resource, int idTextView, T [] object): In questo caso a differenza dell'altro costruttore bisogna specificare - oltre all'id della TextView - anche il layout resource dove cercare la TextView
  • public ArrayAdapter(Context context, int resource, int idTextView, List<T> object): Idem come sopra, con la differenza che al posto dell'array abbiamo una lista di oggetti.

Ne esistono altri ma questi sono quelli che utilizzeremo in questo tutorial. Per prima cosa dobbiamo definire un layout per l'elemento dove all'interno è dichiarata una TextView. Il file lo chiameremo row.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/textViewList"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="" android:padding="10dip"
        android:textSize="22dip"/>

</LinearLayout>

Questo è un layout che compone ogni elemento della nostra lista a partire da un'array che nel nostro caso è un'array di String. Vediamo subito un esempio completo:

import android.app.Activity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;

public class DemoActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        ListView listView = (ListView)findViewById(R.id.listViewDemo);
        String [] array = {"Antonio","Giovanni","Michele","Giuseppe", "Leonardo", "Alessandro"};
        ArrayAdapter<String> arrayAdapter =
	        new ArrayAdapter<String>(this, R.layout.row, R.id.textViewList, array);
        listView.setAdapter(arrayAdapter);
    }
}

L'esempio qui sopra produce questo risultato:

Esempio ListView ArrayAdapter

Analizziamo meglio il codice che implementa la lista. Nella riga di codice seguente non abbiamo fatto altro che avere un riferimento alla lista del layout principale. Questo riferimento ci serve per settare l'adapter:

ListView listView = (ListView)findViewById(R.id.listViewDemo);

In seguito abbiamo creato l'array di String da visualizzare nella lista e poi abbiamo creato l'adapter:

String [] array = {"Antonio","Giovanni","Michele","Giuseppe", "Leonardo", "Alessandro"};
ArrayAdapter<String> arrayAdapter =
            new ArrayAdapter<String>(this, R.layout.row, R.id.textViewList, array);

Nel costruttore dell'adapter abbiamo passato il this che identifica il Context corrente. Con R.layout.row abbiamo specificato il layout dove andare a cercare la TextView (R.id.textViewList) per visualizzare il toString() dell'elemento ed infine abbiamo passato l'array di stringhe da visualizzare.

Esistono all'interno della piattaforma android delle risorse già predefinite da utilizzare all'interno dell'adapter senza implementare nessuna risorsa XML. Queste risorse sono implementate già all'interno della classe android.R. Abbiamo due layout predefiniti che sono android.R.layout.simple_list_item_1 e android.R.layout.simple_list_item_2. Vediamo un semplice esempio:

...
ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, array);
listView.setAdapter(arrayAdapter);
...

Custom Adapter

Fino a questo momento abbiamo creato una lista che visualizza dei semplici elementi di testo partendo da un array di String. Adesso, non sempre abbiamo bisogno di rappresentare dei dati semplici come le String, ma capita spesso nel voler visualizzare degli elementi strutturalmente più complessi. In quest'ultimo caso bisogna implementare dei componenti personalizzati.

L'ArrayAdapter implementa indirettamente l'interfaccia Adapter quindi contiene il metodo getView(). Per produrre un componente personalizzato si procede proprio nella ridefinizione di questo metodo. Innanzitutto partiamo nell'implementare un'oggetto più complesso da visualizzare nella lista. Mettiamo il caso di dover visualizzare una lista di contatti con nome, cognome e numero di telefono. Abbiamo la necessità di visualizzare il nome e cognome sulla stessa riga, mentre il numero di telefono in basso con un colore verde. Incominciamo quindi a definire un layout capace di visualizzare i dati così come ci siamo proposti (il file chiamatelo rowcustom.xml):

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/textViewName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="" />

    <TextView
        android:id="@+id/textViewNumber"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text=""
        android:textColor="#00AA00"
        />

</LinearLayout>

Abbiamo quindi implementato un LinearLayout che incapsula due TextView su due righe, proprio come ci eravamo proposti. Adesso passiamo alla definizione di una classe Contatto, che implementa al suo interno tre campi di tipo String, nome, cognome e numero di telefono:

public class Contatto {

    private String nome;
    private String cognome;
    private String telefono;

    public Contatto(String nome, String cognome, String telefono) {
        this.nome = nome;
        this.cognome = cognome;
        this.telefono = telefono;
    }

    public String getNome() {
        return nome;
    }

    public void setNome(String nome) {
        this.nome = nome;
    }

    public String getCognome() {
        return cognome;
    }

    public void setCognome(String cognome) {
        this.cognome = cognome;
    }

    public String getTelefono() {
        return telefono;
    }

    public void setTelefono(String telefono) {
        this.telefono = telefono;
    }

}

Adesso dobbiamo definire il nostro custom adapter. Tutto avviene attraverso un'operazione di inflating. L'inflating ci permette di istanziare un'oggetto da una risorsa XML. Quindi, ridefiniamo il metodo getView() della classe ArrayAdapter e all'interno del metodo componiamo la visualizzazione dell'elemento della nostra lista. Vediamo il codice dell'intero adapter e dopo cerchiamo di spiegare i singoli passaggi:

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

public class CustomAdapter extends ArrayAdapter<Contatto>{

    public CustomAdapter(Context context, int textViewResourceId,
            Contatto [] objects) {
        super(context, textViewResourceId, objects);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        LayoutInflater inflater = (LayoutInflater) getContext()
             .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        convertView = inflater.inflate(R.layout.rowcustom, null);
        TextView nome = (TextView)convertView.findViewById(R.id.textViewName);
        TextView numero = (TextView)convertView.findViewById(R.id.textViewNumber);
        Contatto c = getItem(position);
        nome.setText(c.getNome()+" "+c.getCognome());
        numero.setText(c.getTelefono());
        return convertView;
    }

}

Dunque, abbiamo ottenuto un riferimento al LayoutInflater di sistema che ci permette di assegnare al convertView il layout che abbiamo definito in XML. Ciò ci ha consentito di avere accesso alle TextView nome e numero di telefono. Tramite al metodo getItem(position) prendiamo l'oggetto Contatto e settiamo i valori nelle rispettive TextView. Come vedete il procedimento è semplice, ma non del tutto efficiente, in quanto è molto pesante in termini di risorse specie quando abbiamo a che fare con liste con un numero elevato di elementi. Ci vuole un procedimento di ottimizzazione che è quello che vedremo nel prossimo paragrafo. Intanto vi lascio l'Activity che implementa il custom adapter che abbiamo visto sopra:

import java.util.LinkedList;
import java.util.List;
import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView;

public class DemoActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        ListView listView = (ListView)findViewById(R.id.listViewDemo);
        List list = new LinkedList();
        list.add(new Contatto("Antonio","Coschignano","1234567890"));
        list.add(new Contatto("Giovanni","Rossi","1234567890"));
        list.add(new Contatto("Giuseppe","Bianchi","1234567890"));
        list.add(new Contatto("Leonardo","Da Vinci","1234567890"));
        list.add(new Contatto("Mario","Rossi","1234567890"));
        list.add(new Contatto("Aldo","Rossi","1234567890"));
        CustomAdapter adapter = new CustomAdapter(this, R.layout.rowcustom, list);
        listView.setAdapter(adapter);
    }
}

Questo è il risultato:

Esempio ListView ArrayAdapter

Ottimizzazione dell'adapter

Nell'esempio che abbiamo visto sopra l'inflating e il metodo findByView() vengono eseguiti per ogni elemento della lista. Queste due operazioni sono di per se pesanti in termini di risorse, quindi dobbiamo adottare delle tecniche che ci consentono di ridurre l'accesso a questi metodi. Possiamo implementare un meccanismo che ci permette di eseguire queste operazioni una sola volta nella visualizzazione della lista, rendendo l'adapter molto più efficiente. Vediamo il codice dell'adapter (CustomAdapterOptimize):

import java.util.List;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

public class CustomAdapterOptimize extends ArrayAdapter<Contatto> {

    public CustomAdapterOptimize(Context context, int textViewResourceId,
                 List<Contatto> objects) {
        super(context, textViewResourceId, objects);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        return getViewOptimize(position, convertView, parent);
    }

    public View getViewOptimize(int position, View convertView, ViewGroup parent) {
        ViewHolder viewHolder = null;
        if (convertView == null) {
            LayoutInflater inflater = (LayoutInflater) getContext()
                      .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = inflater.inflate(R.layout.rowcustom, null);
            viewHolder = new ViewHolder();
            viewHolder.name = (TextView)convertView.findViewById(R.id.textViewName);
            viewHolder.number = (TextView)convertView.findViewById(R.id.textViewNumber);
            convertView.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) convertView.getTag();
        }
        Contatto contatto = getItem(position);
        viewHolder.name.setText(contatto.getNome()+" "+contatto.getNome());
        viewHolder.number.setText(contatto.getTelefono());
        return convertView;
    }

    private class ViewHolder {
        public TextView name;
        public TextView number;
    }
}
La classe View ci consente - tramite il metodo setTag() - di associare un oggetto alla propria istanza di classe. Nel nostro caso abbiamo creato la classe ViewHolder che utilizzeremo per mantenere i riferimenti alle textview del nome e del numero di telefono. Cosi facendo possiamo mantenere i riferimenti solo alla prima invocazione del metodo getView(), in questo modo si evita di ripetere l'operazione per ogni elemento della lista.

Nel caricamente della lista, tramite questo adapter possono verificarsi due stati di caricamento. Il primo quando il convertView è uguale a null, quindi ci troviamo al primo elemento della lista. In questo caso eseguiamo l'inflating e assegniamo gli elementi al viewHolder creandone un'istanza e settandolo nella View attraverso il setTag(). Cosi facendo il viewHolder viene reso disponibile nei successivi caricamenti. Nel secondo caso, quando il convertView è diverso da null, procediamo solo nell'ottenere il riferimento al ViewHolder:

...
} else {
    viewHolder = (ViewHolder) convertView.getTag();
}
...

ListView e listeners

Per listeners intendiamo la possibilità di catturare gli eventi che vengono generati quando facciamo click o selezioniamo un qualsiasi elemento della lista. Ciò ci permette di associare all'evento un'azione che implementiamo all'interno di un'interfaccia specifica. Vediamo alcune delle interfacce che possiamo implementare:

  • OnItemClickListener: Questo listener gestisce l'azione che viene generata quando facciamo click su un'elemento della lista:
    OnItemClickListener clickListener = new OnItemClickListener() {
    
        @Override
        public void onItemClick(AdapterView<?> adapter, View view,
            int position, long id) {
            ...
        }
    };
    listView.setOnItemClickListener(clickListener);
    
  • OnItemLongClickListener: Questo invece viene invocato quando facciamo un click 'lungo' su un elemento:
    OnItemLongClickListener longClickListener = new OnItemLongClickListener() {
    
        @Override
        public void onItemLongClick(AdapterView<?> adapter, View view,
            int position, long id) {
            ...
        }
    };
    listView.setOnItemLongClickListener(longClickListener);
    

Come vedete tutti i metodi delle interfacce ricevono come argomenti l'adapter, la view, la posizione e l'id. Attraverso questi argomenti possiamo accedere all'elemento che abbiamo cliccato. Prendiamo il caso dell'OnItemClickListener. Se abbiamo bisogno di accedere al contatto (che abbiamo implementato sopra) che è associato all'elemento che abbiamo cliccato, basta procedere in questo modo:

@Override
public void onItemClick(AdapterView<?> adapter, View view,
    int position, long id) {
    Contatto c = (Contatto)adapter.getItem(position);
}

L'argomento delle ListView è molto complesso, quello che abbiamo visto in questa guida di certo non pretende di aver esaurito l'argomento. Esistono altri tipi di adapter e altri tipi di liste strutturalmente più complesse come ExpandlibleListView oppure la GridView che usufruiscono di altri tipi di adapter. Di certo tratteremo questi argomenti nei prossimi articoli.

Altri link che potrebbero interessarti
  • » Struttura di un'applicazione Android
  • » Screenshot su un dispositivo Android
  • » Pubblicare applicazioni nell'Android Market
  • » Integrare Google Analytics nelle applicazioni Android
  • » Integrare AdMob nelle applicazioni Android
  • » Guida all'installazione dell'Android SDK
  • » Gestire Thread e GUI nelle applicazioni Android
  • » Applicazioni Android ed il supporto multilingue
  • » Android VideoView e le Youtube Api
  • » Android Linkify e i collegamenti testuali
  • » Android HTML TextView
  • » Android e tecniche multithreading
  • » Android e la gestione dei sensori
  • » Android e l'interfaccia Parcelable

  • Lascia un commento

    Nome :
    E-mail :
    Commento :

    Tutti i commenti inseriti devono essere approvati da un amministratore prima di essere visualizzati al pubblico. Si tratta di una misura preventiva contro spam e pubblicità e non è necessario reinviare il commento. Si prega di scrivere commenti in tema. Spam e messaggi promozionali non vengono approvati.