ListView è un componente Android di base per visualizzare elenchi verticali, disponibile dall'API 1 (Android 1.0). ListView crea un elenco di elementi utilizzando ArrayAdapter e supporta layout di cella personalizzati. Scopriamo cos'è ListView, come configurarlo e quali insidie nasconde questo componente vecchio ma ancora utilizzato. In IT Sectr incontriamo ListView in progetti legacy e manteniamo la compatibilità con Android 4.4. Per un confronto con l'alternativa moderna, leggi l'articolo su RecyclerView.
Punti chiave
ListView è un ViewGroup del pacchetto android.widget, progettato per visualizzare un elenco scorrevole di elementi. ListView implementa il pattern AdapterView: i dati vengono passati tramite un adattatore (Adapter), che converte gli elementi di dati in Views visualizzate sullo schermo. ListView esiste nell'SDK Android dalla prima versione (API 1) e non richiede librerie aggiuntive.
L'architettura di ListView è più semplice di RecyclerView: LayoutManager è integrato (solo scorrimento verticale), le animazioni di modifica sono assenti, i divisori vengono impostati tramite l'attributo XML android:divider. ListView aggiunge automaticamente OverScrollView e l'evidenziazione del focus, semplificando la prototipazione. Secondo Statista (2025), circa il 15% delle app Android nella top 100 del Play Store contiene ancora ListView — per lo più progetti legacy con supporto minimo.
ListView è stato il componente principale per gli elenchi in Android dal 2008 al 2014, quando RecyclerView è stato presentato al Google I/O 2014. Nonostante sia parzialmente deprecato (soft-deprecated), ListView rimane disponibile in tutte le versioni di Android e non è marcato come @Deprecated nell'SDK API 35. Se il tuo progetto supporta Android 4.4 (API 19) e versioni precedenti, ListView è l'unica scelta integrata poiché RecyclerView richiede AndroidX e dipendenze Gradle.
ArrayAdapter è l'adattatore più popolare per ListView. Prende una List<T> e un layout per ogni elemento. Per impostazione predefinita, ArrayAdapter chiama toString() su ogni oggetto e visualizza il testo in simple_list_item_1 (un singolo TextView). Per la visualizzazione personalizzata, è necessario sovrascrivere getView() o passare un layout personalizzato al costruttore.
// ListView con ArrayAdapter (testo semplice)
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val countries = listOf(
"Russia", "USA", "Germania",
"Francia", "Giappone", "Cina", "Brasile"
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val adapter = ArrayAdapter<String>(
this,
android.R.layout.simple_list_item_1,
countries
)
binding.listView.adapter = adapter
// Gestione clic
binding.listView.onItemClickListener =
AdapterView.OnItemClickListener { _, _, position, _ ->
Toast.makeText(this, countries[position], Toast.LENGTH_SHORT).show()
}
}
}
SimpleAdapter è progettato per elenchi da dati Map. Prende una List<Map<String, Any>> e un array from/to per associare le chiavi Map agli ID dei widget nel layout. SimpleAdapter è comodo per prototipi rapidi quando non è necessaria una classe adattatore separata. Lo svantaggio è la mancanza di tipizzazione sicura e la complessità con la logica personalizzata.
<!-- Layout con ListView -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@color/divider"
android:dividerHeight="1px"
android:scrollbars="vertical" />
</LinearLayout>
L'adattatore personalizzato per ListView sovrascrive il metodo getView(), che RecyclerView divide in onCreateViewHolder + onBindViewHolder. In ListView, entrambi i passaggi sono combinati, quindi lo sviluppatore deve implementare indipendentemente il pattern ViewHolder tramite convertView (View riutilizzabile) e il metodo setTag().
// Modello dati
data class Product(
name: String,
price: String,
imageResId: Int
)
// Adattatore personalizzato con ViewHolder
class ProductAdapter(
context: Context,
private val products: List<Product>
) : ArrayAdapter<Product>(context, 0, products) {
// ViewHolder per caching
private class ViewHolder(view: View) {
val nameText: TextView = view.findViewById(R.id.tvName)
val priceText: TextView = view.findViewById(R.id.tvPrice)
val imageView: ImageView = view.findViewById(R.id.ivProduct)
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val view = convertView ?: LayoutInflater.from(context)
.inflate(R.layout.item_product, parent, false)
.also { it.tag = ViewHolder(it) }
val holder = view.tag as ViewHolder
val product = getItem(position)
holder.nameText.text = product.name
holder.priceText.text = product.price
holder.imageView.setImageResource(product.imageResId)
return view
}
}
Differenza principale da RecyclerView: in ListView, il metodo getView() viene chiamato ogni volta che un elemento appare sullo schermo. Se convertView e ViewHolder non vengono utilizzati, ogni scorrimento crea un nuovo oggetto View, causando perdite di memoria e rallentamenti su elenchi di oltre 50 elementi. ViewHolder con view.tag è un'ottimizzazione obbligatoria per ListView con layout personalizzati.
ListView ha quattro limitazioni architetturali che Google ha risolto in RecyclerView: (1) solo orientamento verticale — niente griglie, elenchi orizzontali o a cascata; (2) nessuna notifica granulare — qualsiasi modifica dei dati attiva un ridisegno completo tramite notifyDataSetChanged(), causando tremolii; (3) nessuna animazione integrata di aggiunta/rimozione elementi; (4) nessuna separazione delle responsabilità — LayoutManager è integrato, ItemAnimator è assente.
<!-- item_product.xml — layout elemento ListView -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="12dp">
<ImageView
android:id="@+id/ivProduct"
android:layout_width="56dp"
android:layout_height="56dp"
android:scaleType="centerCrop" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:paddingStart="12dp">
<TextView
android:id="@+id/tvName"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvPrice"
android:textSize="14sp"
android:textColor="@color/price" />
</LinearLayout>
</LinearLayout>
Implicazioni pratiche: su elenchi fino a 20 elementi, ListView funziona veloce quanto RecyclerView. Con oltre 100 elementi, la differenza diventa evidente: ListView inizia a perdere FPS a causa della mancanza di calcoli diff e del ViewHolder forzato. Per elenchi con aggiornamenti frequenti (chat, feed, log), ListView non è adatto — usa RecyclerView con Paging 3. In IT Sectr, migriamo ListView a RecyclerView ogni volta che lavoriamo su schermate legacy con più di 50 elementi.
ListView fornisce un insieme di attributi XML per una configurazione rapida senza codice: android:divider (colore/drawable del divisore), android:dividerHeight (spessore), android:scrollbars (barre di scorrimento), android:fastScrollEnabled (scorrimento rapido tramite maniglia), android:choiceMode (modalità di selezione: singleChoice, multipleChoice). L'attributo android:entries consente di popolare l'elenco da un array di risorse (string-array) senza adattatore.
| Attributo | Descrizione | Valore di esempio |
|---|---|---|
| android:divider | Colore o drawable del divisore | @color/gray_light |
| android:dividerHeight | Spessore del divisore in px/dp | 1dp |
| android:entries | Array di stringhe dalle risorse | @array/countries |
| android:choiceMode | Modalità di selezione elemento | singleChoice |
| android:fastScrollEnabled | Scorrimento rapido | true |
| android:overlapAnchor | Ancora per elenchi a comparsa (API 21+) | true |
Modalità di selezione: choiceMode="singleChoice" attiva lo stile RadioButton in cui l'elemento selezionato viene evidenziato. choiceMode="multipleChoice" — caselle di controllo per più elementi. I dati di selezione sono disponibili tramite listView.checkedItemPosition o listView.checkedItemIds. Importante: con choiceMode, ListView usa StateListDrawable per lo sfondo — imposta il tuo selettore se il blu standard non corrisponde al design.
La scelta tra ListView e RecyclerView dipende dai requisiti del progetto. RecyclerView è lo standard moderno, ma ListView può essere giustificato per schermate semplici con costi di manutenzione minimi per codice legacy. Confronto su tutti i parametri chiave:
| Parametro | ListView | RecyclerView |
|---|---|---|
| Dimensione libreria | Integrato nell'SDK (≈60 KB) | AndroidX (≈300 KB con dipendenze) |
| Versione minima | API 1 | API 14 (tramite AndroidX) |
| Orientamento | Solo verticale | Verticale, orizzontale, griglia, a cascata |
| ViewHolder | Implementazione manuale | Obbligatorio per architettura |
| Animazione | Solo transizioni dissolvenza | DefaultItemAnimator (add, remove, move, change) |
| Aggiornamenti granulari | No (solo notifyDataSetChanged) | notifyItemInserted/Removed/Changed |
| Listener clic | onItemClickListener (integrato) | Tramite ViewHolder (non integrato) |
| Prestazioni | Sufficiente per elenchi fino a 50 elementi | 60 FPS stabili per oltre 1000 elementi |
Conclusione: per i nuovi progetti, usa RecyclerView. Lascia ListView solo nel codice legacy dove la migrazione è irragionevolmente costosa. Secondo le linee guida di Material Design 3 (2026), gli elenchi di qualsiasi complessità dovrebbero usare RecyclerView per soddisfare gli standard moderni di prestazioni e accessibilità.
Domande frequenti
ListView non è marcato come @Deprecated nell'SDK Android API 35, ma Google non ne raccomanda l'uso nei nuovi progetti (soft-deprecated). Il componente rimane funzionante in tutte le versioni di Android. RecyclerView è la sostituzione ufficiale dal 2014. Se stai iniziando un nuovo progetto — scegli RecyclerView. Se stai mantenendo un progetto esistente — ListView continuerà a funzionare senza modifiche.
ListView supporta i metodi addHeaderView(View, Object, boolean) e addFooterView(View, Object, boolean). Header e footer vengono aggiunti all'elenco generale degli elementi ma non partecipano all'adattatore — vengono sempre visualizzati all'inizio e alla fine dell'elenco. Importante: aggiungi header/footer prima di impostare l'adattatore (setAdapter()), altrimenti non verranno inclusi. Dopo aver aggiunto un header, gli ID di posizione degli elementi vengono spostati del numero di header.
Sia ListView che ScrollView gestiscono i gesti verticali, creando un conflitto: lo ScrollView esterno intercetta lo scorrimento e ListView non scorre. Soluzioni: (1) non annidare ListView in ScrollView — usa RecyclerView con NestedScrollingChild, (2) sostituisci ScrollView con NestedScrollView (API 21+), (3) usa ListView con altezza fissa (android:layout_height="...dp") all'interno di ScrollView, ma questo romperà l'adattabilità.
La pulizia dei dati in ListView dipende dal tipo di adattatore. Per ArrayAdapter: adapter.clear() e adapter.addAll(newList) seguiti da adapter.notifyDataSetChanged(). Per adattatore personalizzato: aggiorna l'elenco dati e chiama notifyDataSetChanged(). Nota: listView.invalidateViews() ridisegna solo le View ma non aggiorna i dati. Modifica sempre i dati tramite l'adattatore, non direttamente.
L'altezza di un elemento ListView viene impostata nel layout dell'elemento. Se il layout usa layout_height="wrap_content", l'altezza si adatta al contenuto. Per un'altezza fissa per tutti gli elementi, usa un adattatore personalizzato che imposta LayoutParams in getView(): view.layoutParams.height = resources.getDimensionPixelSize(R.dimen.itemHeight). Per altezze variabili, usa RecyclerView con diversi tipi di vista.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche