Personnaliser une ListView

Le Trocquer Mickaël

Cet article est une introduction à la manipulation des listes sous Android via le composant ListView. Il permet de comprendre les bases de l'affichage des items.

Commentez ♪

- Titre : Personnaliser une ListView

- Auteur : Le Trocquer Mickaël

- Parution : 20 avril 2010

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2010 Le Trocquer Mickaël. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Présentation

- II. Liste de chaines de caractères

- II-A. Affichage avec une ListActivity

- II-B. Affichage sans ListActivity

- III. Personnalisation des items

- IV. Ajout d'évènements

- V. Conclusion

- VI. Remerciements

- VII. Liens

I - Présentation

Sous Android, le composant principal pour afficher et gérer une liste de données est le composant ListView. Par défaut ce composant affiche une liste de chaines de caractères. Selon les besoins, il peut être nécessaire d'afficher plus d'informations sur chaque ligne de la liste, ou même de venir effectuer des opérations particulières suite à des actions sur les lignes.

Dans un premier temps nous allons voir comment afficher une liste toute simple ne contenant que des chaines de caractères, puis nous verrons comment personnaliser l'affichage des items de la liste et enfin comment ajouter des actions sur les items.

II - Liste de chaines de caractères

Le SDK nous propose un type d'Activity particulier pour les vues affichant une liste : ListActivity. Cette activity intègre de base pas mal de mécanismes pour gérer les listes.

Il y a également moyen de gérer tout depuis le début et d'afficher une liste depuis une Activity classique.

II-A - Affichage avec une ListActivity

Dans un premier temps, créez un nouveau projet (« Dvp_ListActivity » par exemple). Choisissez le SDK que vous souhaitez, pour ma part, je me baserai sur la dernière version (2.1), mais le code est également valide pour les versions antérieures.

Sur le wizard de création de projets, il vous est proposé de créer également une Activity, on va en profiter pour le faire tout de suite.

Choisissez comme nom pour votre Activity : « DVPList1 ». Le layout associé sera « main.xml »

Voici ce que vous devriez avoir sur votre wizard de création de projets :

Maintenant on va changer le type de notre DVPList1. À l'heure actuelle, c'est une Activity standard. Nous allons changer ça et la remplacer par une ListActivity.

public class DVPList1 extends Activity {

devient :

public class DVPList1 extends ListActivity {

Maintenant, notre activité sait gérer naturellement une liste de données. Le problème c'est qu'on n'en a pas encore créé dans notre layout. Pour ce faire, on ouvre le fichier main.xml et on ajoute le composant ListView.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_height="wrap_content"
android:text="@string/hello"
/>
</LinearLayout>

devient :

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

Une chose importante à ce niveau-là est l'identifiant du composant ListView : « @android:id/list ». Ici on ne peut pas mettre un identifiant personnel comme on peut le faire pour les autres composants, il faut mettre celui indiqué pour que les mécanismes de gestion de vues (ceux présents dans ListActivity) arrivent à bien fonctionner. En effet, en mettant un identifiant personnalisé, l'objet ListActivity n'arriverait pas à retrouver notre composant ListView dans notre layout et on obtiendrait des exceptions du style :

Caused by: java.lang.RuntimeException: Your content must have a ListView whose id attribute is 'android.R.id.list'

Maintenant que notre layout contient une liste et que notre activité sait la gérer, on peut donc remplir celle-ci. Pour ce faire, nous allons tout d'abord initialiser un tableau de chaines de caractères de la manière suivante :

private String[] mStrings = {
"AAAAAAAA", "BBBBBBBB", "CCCCCCCC", "DDDDDDDD", "EEEEEEEE",
"FFFFFFFF", "GGGGGGGG", "HHHHHHHH", "IIIIIIII", "JJJJJJJJ",
"KKKKKKKK", "LLLLLLLL", "MMMMMMMM", "NNNNNNNN", "OOOOOOOO",
"PPPPPPPP", "QQQQQQQQ", "RRRRRRRR", "SSSSSSSS", "TTTTTTTT",
"UUUUUUUU", "VVVVVVVV", "WWWWWWWW", "XXXXXXXX", "YYYYYYYY",
"ZZZZZZZZ"
};

Ici l'initialisation du tableau est statique, mais on peut très bien imaginer que les informations contenues dans ce tableau proviennent par exemple d'un webservice, d'un fichier XML ou d'un provider du système, etc.

Il ne nous reste plus qu'à afficher ces données dans notre liste. Pour ce faire, nous allons utiliser les fonctionnalités proposées par ListActivity :

public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mStrings);

setListAdapter(adapter);
}

L'adapter permet de gérer les données et de réaliser le mapping relationnel entre les données et l'IHM. Le premier paramètre (this) permet d'identifier le contexte dans lequel notre adapter va fonctionner. Le deuxième paramètre « android.R.layout.simple_list_item_1 » permet d'indiquer la présentation utilisée pour les items de notre liste. Ici il s'agit d'une présentation simple pour les chaines de caractères incluses dans le SDK.

L'appel de cette méthode permet donc d'afficher notre liste. Ainsi si nous exécutons notre projet, nous obtenons ceci :

Les sources de cette application sont téléchargeables ici : DVP_ListActivity.zip

II-B - Affichage sans ListActivity

Maintenant que nous avons vu comment bénéficier des avantages des ListActivity, nous allons voir comment reproduire ce mécanisme de gestion de listes de A à Z. Pour ce faire, nous allons repartir de zéro et récréer un nouveau projet comme indiqué sur l'image suivante :

Maintenant nous allons éditer le fichier « main.xml » afin de lui rajouter un composant ListView.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_height="wrap_content"
android:text="@string/hello"
/>
</LinearLayout>

devient :

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

<ListView android:id="@+id/ListView01"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</ListView>
</LinearLayout>

Dans notre Activity DVPList1, de la même façon que dans l'exemple précédent, nous allons initialiser un tableau de String de manière statique (à titre d'exemple) :

private String[] mStrings = {
"AAAAAAAA", "BBBBBBBB", "CCCCCCCC", "DDDDDDDD", "EEEEEEEE",
"FFFFFFFF", "GGGGGGGG", "HHHHHHHH", "IIIIIIII", "JJJJJJJJ",
"KKKKKKKK", "LLLLLLLL", "MMMMMMMM", "NNNNNNNN", "OOOOOOOO",
"PPPPPPPP", "QQQQQQQQ", "RRRRRRRR", "SSSSSSSS", "TTTTTTTT",
"UUUUUUUU", "VVVVVVVV", "WWWWWWWW", "XXXXXXXX", "YYYYYYYY",
"ZZZZZZZZ"
};

Maintenant, nous allons créer un adapter (comme dans l'exemple précédent) afin de fournir notre liste de données au composant ListView :

//Création de l'adapter
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mStrings);

//Récupération du ListView présent dans notre IHM
ListView list = (ListView)findViewById(R.id.ListView01);

//On passe nos données au composant ListView
list.setAdapter(adapter);

Voici ce que nous obtenons à l'exécution :

Comme vous pouvez le voir, il n'y a pas de différences notables entre les deux versions, le rendu est le même. Pour l'instant le seul avantage d'utiliser une ListActivity par rapport à un Activity classique réside dans le fait qu'il n'y a pas besoin de récupérer l'instance du composant ListView dans l'IHM.

Les sources de cette application sont téléchargeables ici : DVP_List1.zip

III - Personnalisation des items

Dans cette partie nous allons voir comment personnaliser notre liste, et surtout comment personnaliser la présentation des items de la liste. Le thème de cet exercice sera d'afficher une liste de personnes. Une personne aura un nom, un prénom, un genre (Masculin / Féminin).

Dans un premier temps, nous allons créer un projet « DVP_List2 », tel que décrit sur ce wizard :

Ensuite, nous allons créer une classe pour représenter les personnes. Cette classe contient trois propriétés : « nom », « prenom » et « genre ». Afin de simplifier les développements et l'exemple, on rajoute une méthode static dans cette classe pour remplir et renvoyer une liste de personnes. Le code de la classe donne donc :

package com.dvp.list;

import java.util.ArrayList;

public class Personne {
public final static int MASCULIN = 1;
public final static int FEMININ = 2;

public String nom;
public String prenom;
public int genre;

public Personne(String aNom, String aPrenom, int aGenre) {
nom = aNom;
prenom = aPrenom;
genre = aGenre;
}

/**
* Initialise une liste de personnes
* @return une liste de "Personne"
*/
public static ArrayList<Personne> getAListOfPersonne() {
ArrayList<Personne> listPers = new ArrayList<Personne>();

listPers.add(new Personne("Nom1", "Prenom1", FEMININ));
listPers.add(new Personne("Nom2", "Prenom2", MASCULIN));
listPers.add(new Personne("Nom3", "Prenom3", MASCULIN));
listPers.add(new Personne("Nom4", "Prenom4", FEMININ));
listPers.add(new Personne("Nom5", "Prenom5", FEMININ));
listPers.add(new Personne("Nom6", "Prenom6", MASCULIN));
listPers.add(new Personne("Nom7", "Prenom7", FEMININ));
listPers.add(new Personne("Nom8", "Prenom8", MASCULIN));
listPers.add(new Personne("Nom9", "Prenom9", MASCULIN));
listPers.add(new Personne("Nom10", "Prenom10", FEMININ));
listPers.add(new Personne("Nom11", "Prenom11", MASCULIN));
listPers.add(new Personne("Nom12", "Prenom12", MASCULIN));
listPers.add(new Personne("Nom13", "Prenom13", FEMININ));
listPers.add(new Personne("Nom14", "Prenom14", MASCULIN));

return listPers;
}
}

Maintenant, dans le layout XML (main.xml), rajoutez le composant ListView. Le code XML du layout doit correspondre à ceci :

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

<ListView android:id="@+id/ListView01"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</ListView>
</LinearLayout>

Les personnes possèdent trois propriétés. Il n'est donc plus possible de passer par le layout standard pour les afficher. Nous allons donc créer un nouveau layout, et l'utiliser pour afficher les items de la liste. Ajoutez un nouveau fichier XML dans le répertoire layout de votre application. (Clic droit sur votre projet, choisissez « New » puis « Android XML file ».) Configurez le wizard tel qu'indiqué sur l'image suivante :

Dans ce nouveau layout, nous allons donc ajouter deux TextView pour décrire les nom et prénom de chaque personne. Le genre de la personne sera décrit par la couleur de fond de notre item : bleu pour les garçons, rose pour les filles :). Le XML de notre item doit donc ressembler à ça :

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

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

<TextView android:text="Prénom" android:id="@+id/TV_Prenom"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</TextView>

</LinearLayout>

Il ne nous reste plus qu'à afficher la liste des personnes en utilisant notre layout. Pour ce faire, nous allons créer un objet qui se chargera de gérer le mapping entre nos données et le layout des items. Ce composant sera basé sur un Adapter. Dans Eclipse, créez une nouvelle classe : « PersonneAdapter ». Faites-la hériter de « BaseAdapter ».

public class PersonneAdapter extends BaseAdapter {

L'Adapter va gérer une liste de personnes et va s'occuper également de l'affichage. On va donc lui ajouter trois propriétés

// Une liste de personnes
private List<Personne> mListP;

//Le contexte dans lequel est présent notre adapter
private Context mContext;

//Un mécanisme pour gérer l'affichage graphique depuis un layout XML
private LayoutInflater mInflater;

Le constructeur va alors ressembler à ceci :

public PersonneAdapter(Context context, List<Personne> aListP) {
mContext = context;
mListP = aListP;
mInflater = LayoutInflater.from(mContext);
}

Le LayoutInflater permet de parser un layout XML et de te transcoder en IHM Android. Pour respecter l'interface BaseAdapter, il nous faut spécifier la méthode « count() ». Cette méthode permet de connaitre le nombre d'items présents dans la liste. Dans notre cas, il faut donc renvoyer le nombre de personnes contenues dans « mListP ».

public int getCount() {
return mListP.size();
}

Ensuite, il y a deux méthodes pour identifier les items de la liste. Une pour connaitre l'item situé à une certaine position et l'autre pour connaitre l'identifiant d'un item en fonction de sa position.

public Object getItem(int position) {
return mListP.get(position);
}

public long getItemId(int position) {
return position;
}

Maintenant il faut surcharger la méthode pour renvoyer une « View » en fonction d'une position donnée. Cette view contiendra donc une occurrence du layout « personne_layout.xml » convenablement renseignée (avec le nom et prénom au bon endroit).

public View getView(int position, View convertView, ViewGroup parent) {
LinearLayout layoutItem;
//(1) : Réutilisation des layouts
if (convertView == null) {
//Initialisation de notre item à partir du  layout XML "personne_layout.xml"
layoutItem = (LinearLayout) mInflater.inflate(R.layout.personne_layout, parent, false);
} else {
layoutItem = (LinearLayout) convertView;
}

//(2) : Récupération des TextView de notre layout
TextView tv_Nom = (TextView)layoutItem.findViewById(R.id.TV_Nom);
TextView tv_Prenom = (TextView)layoutItem.findViewById(R.id.TV_Prenom);

//(3) : Renseignement des valeurs
tv_Nom.setText(mListP.get(position).nom);
tv_Prenom.setText(mListP.get(position).prenom);

//(4) Changement de la couleur du fond de notre item
if (mListP.get(position).genre == Personne.MASCULIN) {
layoutItem.setBackgroundColor(Color.BLUE);
} else {
layoutItem.setBackgroundColor(Color.MAGENTA);
}

//On retourne l'item créé.
return layoutItem;
}

Le paramètre « convertView » permet de recycler les éléments de notre liste. En effet, l'opération pour convertir un layout XML en IHM standard est très couteuse pour la plateforme Android. On nous propose ici de réutiliser des occurrences créées qui ne sont plus affichées. Donc si ce paramètre est « null » alors, il faut « inflater » notre layout XML, sinon on le réutilise (1). Maintenant que notre layout est initialisé, on peut récupérer les deux champs texte qui y sont présents (2) afin de modifier leur valeur (3). Afin de respecter notre contrat, les garçons doivent être en bleu et les filles en rose. Il nous suffit donc de changer la couleur du fond (4).

Pour résumer, nous avons une vue contenant une liste (main.xml), une description de la présentation d'un item de la liste (personne_layout.xml) et un composant pour gérer le mapping donnée - IHM (PersonneAdapter.java). Il ne nous reste donc plus qu'à afficher notre liste. Pour ce faire, modifiez l'Activity principale, DVPList2.java :

public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

//Récupération de la liste des personnes
ArrayList<Personne> listP = Personne.getAListOfPersonne();

//Création et initialisation de l'Adapter pour les personnes
PersonneAdapter adapter = new PersonneAdapter(this, listP);

//Récupération du composant ListView
ListView list = (ListView)findViewById(R.id.ListView01);

//Initialisation de la liste avec les données
list.setAdapter(adapter);
}

Maintenant, si vous exécutez votre programme, vous devriez obtenir ceci :

Les sources de cette application sont téléchargeables ici : DVP_List2.zip

IV - Ajout d'évènements

Nous allons maintenant voir comment ajouter un mécanisme pour écouter ce qu'il se passe sur notre liste. Nous allons simplement enrichir l'application précédente. Le but de ce prochain exemple sera d'écouter si l'utilisateur clique sur le nom d'une personne.

Dans un premier temps, créer une interface pour votre listener personnalisé. Dans votre fichier « PersonneAdapter.java », rajoutez la partie suivante :

/**
* Interface pour écouter les évènements sur le nom d'une personne
*/
public interface PersonneAdapterListener {
public void onClickNom(Personne item, int position);
}

Ensuite, créez le mécanisme pour gérer l'ajout de listener sur notre adapter :

//Contient la liste des listeners
private ArrayList<PersonneAdapterListener> mListListener = new ArrayList<PersonneAdapterListener>();
/**
* Pour ajouter un listener sur notre adapter
*/
public void addListener(PersonneAdapterListener aListener) {
mListListener.add(aListener);
}

La prochaine méthode permet de prévenir tous les listeners en même temps pour diffuser une information :

private void sendListener(Personne item, int position) {
for(int i = mListListener.size()-1; i >= 0; i--) {
mListListener.get(i).onClickNom(item, position);
}
}

Le mécanisme pour ajouter / prévenir les listeners est en place. On peut ajouter le listener interne sur le clic du TextView du nom des personnes. Pour ce faire, dans la méthode « getView » de l'adapter, ajoutez ceci :

if (mListP.get(position).genre == Personne.MASCULIN) {
layoutItem.setBackgroundColor(Color.BLUE);
} else {
layoutItem.setBackgroundColor(Color.MAGENTA);
}

//------------ Début de l'ajout -------
//On mémorise la position de la "Personne" dans le composant textview
tv_Nom.setTag(position);
//On ajoute un listener
tv_Nom.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
//Lorsque l'on clique sur le nom, on récupère la position de la "Personne"
Integer position = (Integer)v.getTag();

//On prévient les listeners qu'il y a eu un clic sur le TextView "TV_Nom".
sendListener(mListP.get(position), position);

}

});
//------------ Fin de l'ajout -------

return layoutItem;

Maintenant, nous allons retravailler sur notre activity principale (DVPList2.java) pour qu'elle écoute les évènements sur notre liste. Tout d'abord, faites-la implémenter notre interface « PersonneAdapterListener » :

public class DvpList2 extends Activity implements PersonneAdapterListener {

Rajoutez la méthode déclenchée lors d'un clic sur le nom d'une personne. Dans notre cas, un popup s'ouvrira pour donner le nom de la personne cliquée.

public void onClickNom(Personne item, int position) {
Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Personne");

builder.setMessage("Vous avez cliqué sur : " + item.nom);
builder.setPositiveButton("Oui", null);
builder.setNegativeButton("Non", null);
builder.show();
}

Et enfin, branchez votre listener sur votre liste :

public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

ArrayList<Personne> listP = Personne.getAListOfPersonne();
PersonneAdapter adapter = new PersonneAdapter(this, listP);

//Ecoute des évènements sur votre liste
adapter.addListener(this);

ListView list = (ListView)findViewById(R.id.ListView01);

list.setAdapter(adapter);
}

Maintenant, si vous exécutez votre programme, vous devriez obtenir ceci :

Les sources de cette application sont téléchargeables ici : DVP_List2_event.zip

Je tiens également à vous faire remarquer que si vous souhaitez écouter les évènements (onClick) sur une liste depuis une activity de type « ListActivity », il vous suffit de surcharger la méthode onListItemClick :

protected void onListItemClick(ListView l, View v, int position, long id)

À partir de là, vous pourrez récupérer l'item qui a été sélectionné par l'utilisateur. Voici un exemple d'implémentation qui peut être inséré dans la classe DVP_List1.java de l'exemple du paragraphe I :

@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);

Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Item");

builder.setMessage("Vous avez cliqué sur : " + mStrings[position]);
builder.setPositiveButton("Oui", null);
builder.setNegativeButton("Non", null);
builder.show(); }

V - Conclusion

Pour conclure, l'utilisation des composants pour lister les données sous Android peut dans un premier temps paraître très simple pour une utilisation basique, mais offre également de puissants moyens de personnalisation dès que l'on souhaite enrichir un peu plus notre interface. Ce tutoriel vous aura permis de découvrir les bases de ces personnalisations, il y en a encore bien d'autres, notamment les optimisations d'affichage (en plus de celles que l'on a découvertes dans le tutoriel).

VI - Remerciements

Je voudrais remercier Jacques Thery pour sa contribution et ses corrections.

VII - Liens

Site du développeur
FAQ Android
Installation de l'environnement