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.