ListView est un composant Android de base pour afficher des listes verticales, disponible depuis l'API 1 (Android 1.0). ListView crée une liste d'éléments à l'aide d'ArrayAdapter et prend en charge les dispositions de cellule personnalisées. Comprendons ce qu'est ListView, comment le configurer et quels pièges cache ce composant ancien mais toujours utilisé. Chez IT Sectr, nous rencontrons ListView dans des projets legacy et maintenons la rétrocompatibilité avec Android 4.4. Pour une comparaison avec l'alternative moderne, lisez l'article sur RecyclerView.
Points clés
ListView est un ViewGroup du paquet android.widget, conçu pour afficher une liste défilante d'éléments. ListView implémente le modèle AdapterView : les données sont transmises via un adaptateur (Adapter), qui convertit les éléments de données en Views affichées à l'écran. ListView existe dans le SDK Android depuis la première version (API 1) et ne nécessite pas de bibliothèques supplémentaires.
L'architecture de ListView est plus simple que celle de RecyclerView : LayoutManager est intégré (défilement vertical uniquement), les animations de changement sont absentes, les séparateurs sont définis via l'attribut XML android:divider. ListView ajoute automatiquement OverScrollView et le surlignage de focus, ce qui simplifie le prototypage. Selon Statista (2025), environ 15 % des applications Android du top 100 du Play Store contiennent encore ListView — principalement des projets legacy avec un support minimal.
ListView a été le composant de liste principal dans Android de 2008 à 2014, lorsque RecyclerView a été présenté à Google I/O 2014. Bien que partiellement obsolète (soft-deprecated), ListView reste disponible dans toutes les versions d'Android et n'est pas marqué @Deprecated dans le SDK API 35. Si votre projet prend en charge Android 4.4 (API 19) et inférieur, ListView est le seul choix intégré, car RecyclerView nécessite AndroidX et des dépendances Gradle.
ArrayAdapter est l'adaptateur le plus populaire pour ListView. Il prend une List<T> et une disposition pour chaque élément. Par défaut, ArrayAdapter appelle toString() sur chaque objet et affiche le texte dans simple_list_item_1 (un seul TextView). Pour un affichage personnalisé, vous devez remplacer getView() ou passer une disposition personnalisée au constructeur.
// ListView avec ArrayAdapter (texte simple)
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val countries = listOf(
"Russie", "États-Unis", "Allemagne",
"France", "Japon", "Chine", "Brésil"
)
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
// Gestion des clics
binding.listView.onItemClickListener =
AdapterView.OnItemClickListener { _, _, position, _ ->
Toast.makeText(this, countries[position], Toast.LENGTH_SHORT).show()
}
}
}
SimpleAdapter est conçu pour les listes à partir de données Map. Il prend une List<Map<String, Any>> et un tableau from/to pour lier les clés Map aux ID des widgets dans la disposition. SimpleAdapter est pratique pour les prototypes rapides lorsqu'une classe d'adaptateur séparée n'est pas nécessaire. L'inconvénient est l'absence de typage sécurisé et la complexité avec la logique personnalisée.
<!-- Disposition avec 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'adaptateur personnalisé pour ListView remplace la méthode getView(), que RecyclerView divise en onCreateViewHolder + onBindViewHolder. Dans ListView, les deux étapes sont combinées, donc le développeur doit implémenter indépendamment le modèle ViewHolder via convertView (View réutilisable) et la méthode setTag().
// Modèle de données
data class Product(
name: String,
price: String,
imageResId: Int
)
// Adaptateur personnalisé avec ViewHolder
class ProductAdapter(
context: Context,
private val products: List<Product>
) : ArrayAdapter<Product>(context, 0, products) {
// ViewHolder pour la mise en cache
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
}
}
Différence clé avec RecyclerView : dans ListView, la méthode getView() est appelée chaque fois qu'un élément apparaît à l'écran. Si convertView et ViewHolder ne sont pas utilisés, chaque défilement crée un nouvel objet View, entraînant des fuites de mémoire et des ralentissements sur les listes de plus de 50 éléments. ViewHolder avec view.tag est une optimisation obligatoire pour ListView avec des dispositions personnalisées.
ListView a quatre limitations architecturales que Google a corrigées dans RecyclerView : (1) orientation verticale uniquement — pas de grilles, listes horizontales ou cascades ; (2) pas de notifications granulaires — toute modification de données déclenche un redessin complet via notifyDataSetChanged(), provoquant des saccades ; (3) pas d'animation intégrée d'ajout/suppression d'éléments ; (4) pas de séparation des responsabilités — LayoutManager est intégré, ItemAnimator est absent.
<!-- item_product.xml — disposition d'élément 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>
Implications pratiques : sur les listes jusqu'à 20 éléments, ListView fonctionne aussi rapidement que RecyclerView. À partir de 100+ éléments, la différence devient perceptible : ListView commence à perdre des FPS en raison de l'absence de calculs de diff et du ViewHolder forcé. Pour les listes avec des mises à jour fréquentes (chat, flux, logs), ListView n'est pas adapté — utilisez RecyclerView avec Paging 3. Chez IT Sectr, nous migrons ListView vers RecyclerView chaque fois que nous travaillons sur des écrans legacy avec plus de 50 éléments.
ListView fournit un ensemble d'attributs XML pour une configuration rapide sans code : android:divider (couleur/drawable du séparateur), android:dividerHeight (épaisseur), android:scrollbars (barres de défilement), android:fastScrollEnabled (défilement rapide par gestionnaire), android:choiceMode (mode de sélection : singleChoice, multipleChoice). L'attribut android:entries permet de remplir la liste à partir d'un tableau de ressources (string-array) sans adaptateur.
| Attribut | Description | Exemple de valeur |
|---|---|---|
| android:divider | Couleur ou drawable du séparateur | @color/gray_light |
| android:dividerHeight | Épaisseur du séparateur en px/dp | 1dp |
| android:entries | Tableau de chaînes depuis les ressources | @array/countries |
| android:choiceMode | Mode de sélection d'élément | singleChoice |
| android:fastScrollEnabled | Défilement rapide | true |
| android:overlapAnchor | Ancre pour les listes contextuelles (API 21+) | true |
Mode de sélection : choiceMode="singleChoice" active le style RadioButton où l'élément sélectionné est mis en surbrillance. choiceMode="multipleChoice" — cases à cocher pour plusieurs éléments. Les données de sélection sont disponibles via listView.checkedItemPosition ou listView.checkedItemIds. Important : avec choiceMode, ListView utilise StateListDrawable pour l'arrière-plan — définissez votre propre sélecteur si le bleu standard ne correspond pas au design.
Le choix entre ListView et RecyclerView dépend des exigences du projet. RecyclerView est la norme moderne, mais ListView peut être justifié pour des écrans simples avec des coûts de maintenance minimes pour le code legacy. Comparaison sur tous les paramètres clés :
| Paramètre | ListView | RecyclerView |
|---|---|---|
| Taille de la bibliothèque | Intégré au SDK (≈60 Ko) | AndroidX (≈300 Ko avec dépendances) |
| Version minimale | API 1 | API 14 (via AndroidX) |
| Orientation | Verticale uniquement | Verticale, horizontale, grille, en cascade |
| ViewHolder | Implémentation manuelle | Obligatoire par architecture |
| Animation | Transitions par fondu uniquement | DefaultItemAnimator (add, remove, move, change) |
| Mises à jour granulaires | Non (uniquement notifyDataSetChanged) | notifyItemInserted/Removed/Changed |
| Écouteur de clic | onItemClickListener (intégré) | Via ViewHolder (non intégré) |
| Performance | Suffisante pour les listes jusqu'à 50 éléments | 60 FPS stables pour 1000+ éléments |
Conclusion : pour les nouveaux projets, utilisez RecyclerView. Laissez ListView uniquement dans le code legacy où la migration est trop coûteuse. Selon les directives de Material Design 3 (2026), les listes de toute complexité doivent utiliser RecyclerView pour répondre aux normes modernes de performance et d'accessibilité.
Questions fréquentes
ListView n'est pas marqué @Deprecated dans le SDK Android API 35, mais Google ne recommande pas son utilisation dans les nouveaux projets (soft-deprecated). Le composant reste fonctionnel dans toutes les versions d'Android. RecyclerView est le remplacement officiel depuis 2014. Si vous commencez un nouveau projet — choisissez RecyclerView. Si vous maintenez un projet existant — ListView continuera de fonctionner sans changement.
ListView prend en charge les méthodes addHeaderView(View, Object, boolean) et addFooterView(View, Object, boolean). Les en-têtes et pieds de page sont ajoutés à la liste générale des éléments mais ne participent pas à l'adaptateur — ils sont toujours affichés au début et à la fin de la liste. Important : ajoutez l'en-tête/le pied de page avant de définir l'adaptateur (setAdapter()), sinon ils ne seront pas inclus. Après l'ajout d'un en-tête, les ID de position des éléments sont décalés du nombre d'en-têtes.
ListView et ScrollView gèrent tous deux les gestes verticaux, ce qui crée un conflit : le ScrollView externe intercepte le défilement et ListView ne défile pas. Solutions : (1) n'imbriquez pas ListView dans ScrollView — utilisez RecyclerView avec NestedScrollingChild, (2) remplacez ScrollView par NestedScrollView (API 21+), (3) utilisez ListView avec une hauteur fixe (android:layout_height="...dp") dans ScrollView, mais cela cassera l'adaptabilité.
Le vidage des données dans ListView dépend du type d'adaptateur. Pour ArrayAdapter : adapter.clear() et adapter.addAll(newList) suivi de adapter.notifyDataSetChanged(). Pour un adaptateur personnalisé : mettez à jour la liste de données et appelez notifyDataSetChanged(). Remarque : listView.invalidateViews() ne fait que redessiner les Views mais ne met pas à jour les données. Modifiez toujours les données via l'adaptateur, pas directement.
La hauteur d'un élément ListView est définie dans la disposition de l'élément. Si la disposition utilise layout_height="wrap_content", la hauteur s'adapte au contenu. Pour une hauteur fixe pour tous les éléments, utilisez un adaptateur personnalisé qui définit LayoutParams dans getView() : view.layoutParams.height = resources.getDimensionPixelSize(R.dimen.itemHeight). Pour des hauteurs variables, utilisez RecyclerView avec différents types de vue.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi