Nous avons vu ici comment lancer notre première application : Hello, Android.
Puis, ici, nous avons créé un programme interagissant avec l’utilisateur : récupération puis affichage de sa saisie dans un nouvel écran.
Passons aux choses sérieuses maintenant, avec un sujet non trivial : l'affichage d'une liste personnalisée d'items.
2 composants interviennent : la ListView et son Adaptateur de données. Une petite difficulté ici car nous créons un Adaptateur personnalisé.
Puis, ici, nous avons créé un programme interagissant avec l’utilisateur : récupération puis affichage de sa saisie dans un nouvel écran.
Passons aux choses sérieuses maintenant, avec un sujet non trivial : l'affichage d'une liste personnalisée d'items.
2 composants interviennent : la ListView et son Adaptateur de données. Une petite difficulté ici car nous créons un Adaptateur personnalisé.
Au menu :
1. Affichage d’une liste de documents
| 2. Gestion d’un événement
|
Les ingrédients :
- Une liste de documents. Le modèle.
- Des icônes pour représenter les différents types de document. Des ressources graphiques.
- Une activité chargée d'afficher cette liste. La classe java principale.
- Un layout principal contenant le composant ListView. Fichier XML.
- Un layout spécifique pour décrire chaque ligne : l'icône, le nom et la taille du document. Fichier XML.
- Un Adaptateur de données, composant Android associant chaque vue à sa donnée. Ici, nous écrirons un adaptateur personnalisé héritant d'ArrayAdapter spécialisé dans les listes et tableaux. Classe java.
La préparation :
Organisation des sources
- nous créons le package fr.scherrda.android.tuto.list pour nos classes java.
- rappel : les fichiers XML de layout sont à placer sous : res/layout
- les ressources de type graphique (icônes) sont sous : res/drawable-hdpi
Créer le layout principal : res/layout/list.xml
Le layout contient 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"> <ListView android:id="@+id/listdocs" android:layout_width="fill_parent" android:layout_height="fill_parent" android:padding="10dp"/> </LinearLayout> |
3. Créer le layout d'une ligne : res/layout/row_list.xml
Chaque ligne est constituée d'une icône à gauche, puis de 2 textes l'un au dessus de l'autre, le premier étant en caractère Gras:
|
|
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="fill_parent"> <ImageView android:id="@+id/icon" android:layout_height="wrap_content" android:layout_width="wrap_content" android:padding="5dp" android:layout_gravity="center_vertical"> </ImageView> <LinearLayout android:orientation="vertical" android:layout_height="fill_parent" android:layout_width="fill_parent" android:padding="10dp"> <TextView android:id="@+id/name" android:layout_width="fill_parent" android:layout_height="wrap_content" android:textStyle="bold" android:textSize="14sp" /> <TextView android:id="@+id/size" android:layout_width="fill_parent" android:layout_height="fill_parent" android:textSize="10sp" /> </LinearLayout> </LinearLayout> |
4. Créer l’entité Document
package fr.scherrda.android.tuto.list.model;
public class Document{
private String name;
private long size;
private int icone ;
public Document(String name, long size, int icone) {
}
}
|
5. Rajouter des icônes en tant que ressources graphiques dans votre projet
Trouvez des icônes pour représenter les différents types de fichier (icn_video.png pour les video, icn_word.png pour les documents de type texte, icn_image.png pour les documents de type image, ...
Les ressources Image sont à placer sous res/drawable-hdpi (résolution haute que nous utilisons par défaut)
6. Créer l'activité : ListDocumentsActivity
Caractéristiques de cette activité :
- Etend Activity
- Possède un attribut : mListView,
- Implémente OnItemClickListener :
→ elle redéfinit la méthode OnItemClick() ( affichage d’un Toast avec le nom de l’élément sélectionné)
→ nous l’enregistrons auprès de la liste en tant que Listener du clic d'un item de liste
- ArrayDocumentAdapter, notre adaptateur personnalisé pour gérer une liste de documents est créé et associé à la listView, lors de la phase d’initialisation de l’activité.
/**
* initialisation de l’activité
*/
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.list);
mListView = (ListView)findViewById(R.id.listdocs);
//getDocuments : fabrique une liste de documents fixe
List<Document> documents = getDocuments();
//association avec l'adaptateur
mListView.setAdapter(new ArrayDocumentAdapter(this, documents));
//l'activité enregistrée comme listener sur le clic d'item mListView.setOnItemClickListener(this);
}
|
/**
* L'activité implémente OnItemClickListener : redéfinition de onItemClick
*/
@Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
//récupération de l’item sélectionné
Document document =
(Document)mListView.getAdapter().getItem(position);
String name = document.getName();
//affichage d’un message Toast
Toast.makeText(this, "Vous avez cliqué sur le document : " + name,
Toast.LENGTH_LONG).show();
}
|
/**
* retourne une liste de documents
*/
private List<Document> getDocuments() {
List<Document> liste = new ArrayList<Document>();
Document doc1 = new Document(1, "doc1.doc", 10000, R.drawable.icn_word);
liste.add(doc1);
Document doc2 = new Document(2, "MonDocumentPres.ppt", 200000, R.drawable.icn_ppt);
liste.add(doc2);
Document doc3 = new Document(3, "unCalcul.xls", 300000, R.drawable.icn_xls);
liste.add(doc3);
Document doc4 = new Document(4, "doc4.doc", 150000, R.drawable.icn_word);
liste.add(doc4);
return liste;
}
|
A ce stade Eclipse déclare une erreur de compilation :
→ nous utilisons l’assistant d’Eclipse pour créer la classe ArrayDocumentAdapter automatiquement.
Caractéristiques
- étend ArrayAdapter
- possède 2 attributs : mDocuments, la liste de documents, et mInflater de type LayoutInflater, initialisés par le constructeur.
Le processus de création des composants View à partir des ressources Layout est applelé le “layout inflation”. Ce processus est géré par le système de manière sous-jacente lors de la phase de création d’une activité.
- le plus important : la redéfinition de la méthode getView(int position, View view, ViewGroup parent)
- on créé l’objet View représentant la ligne courante à partir de la ressource layout
- on valorise les zones texte et l’icône de la vue
- La méthode retourne la vue ainsi mise à jour.
/**
* Constructeur
*/
public ArrayDocumentAdapter(Context context, List<Document> documents) {
super(context, R.layout.row_list, documents);
this.mDocuments = documents;
mInflater = LayoutInflater.from(context);
}
|
Une version non optimisée de getView
@Override
public View getView(int position, View convertView, ViewGroup parent) {
//création de l’objet View à partir de la ressource Layout
rowView = mInflater.inflate(R.layout.row_list, null);
//récupération du document identifié par sa position dans la liste
Document document = mDocuments.get(position);
//Valorisation du Texte1 de la Vue avec le nom du document
TextView nameView = (TextView) rowView.findViewById(R.id.name);
nameView.setText(document.getName());
//Valorisation du Texte2 avec la taille du document
TextView sizeView = (TextView) rowView.findViewById(R.id.size);
sizeView.setText(String.valueOf(document.getSize()) + "Ko");
//Affichage de l’icône correspondante
ImageView iconeView = (ImageView) rowView.findViewById(R.id.icon);
iconeView.setImageResource(document.getIcone());
return rowView;
}
|
Cette implémentation marche mais n’est pas optimisée. En conséquence, le thread gérant l’affichage de la liste risque de “ramer” et vous constaterez des ralentissements si votre liste à afficher contient beaucoup d’items.
Les problèmes de performance :
Chaque fois que l’utilisateur fait scroller sa liste, ou que l’orientation change, le système doit recalculer l’affichage de la liste. La méthode getView est appelée pour générer l’affichage de chaque ligne de l’écran.
Vous pourrez constater des problèmes de ralentissements si votre liste contient beaucoup d’items.
Or 2 opérations coûtes particulièrement chères et ne doivent être appelées que si nécessaires :
- la création d’objets (ici via le layout inflation). Comme d’habitude en java, il ne faut créer les objets que si nécessaire.
- la recherche d’un composant View dans l’arbre des ressources avec findViewById
Pour optimiser la méthode, nous allons recycler, réutiliser ! Cela évitera de recréer un objet réutilisable ou de rechercher dans notre arborescence de composants, un composant déjà récupéré antérieurement.
La version optimisée de getView
Le recyclage- lorsque un item disparaît de l’écran, sa vue est conservée dans un espace pour le recyclage.
- Réciproquement, lorsqu’un item apparaît à l’écran, s’il existe une vue disponible pour le recyclage, le système la passe à la méthode getView. Il s’agit du paramètre convertView
→ convertView est une vue utilisable pour notre ligne courante. Si convertView est nul, nous créerons l’objet View. Sinon, nous pourrons l’utiliser directement, économisant ainsi une création d’objet.
La mise en cache
- nameView, sizeView et iconeView sont des vues enfants de la vue associée à l’item courant.
Au lieu de les récupérer à chaque fois à partir de l’arborescence avec un findViewById, nous allons les associer une fois pour toute à la vue parente en utilisant le pattern ViewHolder, une classe interne statique :
//membre de classe (ne dépend pas de l'instance de la classe contenante) // utilisée pour stocker des références static class ViewHolder { public TextView nameView; public TextView sizeView; public ImageView iconeView; } |
@Override public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
View rowView = convertView;
if (rowView == null) {
//création de l’objet View à partir de la ressource Layout
rowView = mInflater.inflate(R.layout.row_list, null);
holder = new ViewHolder();
holder.nameView = (TextView) rowView.findViewById(R.id.name);holder.sizeView = (TextView) rowView.findViewById(R.id.size); holder.iconeView = (ImageView) rowView.findViewById(R.id.icon);
rowView.setTag(holder);
}else {
holder = (ViewHolder) rowView.getTag();
}
//récupération du document identifié par sa position dans la liste
Document document = mDocuments.get(position);
//Valorisation du Texte1 de la Vue avec le nom du document
holder.nameView.setText(document.getName());
//Valorisation du Texte2 avec la taille du document
holder.sizeView.setText(String.valueOf(document.getSize()) + "Ko");
//Affichage de l’icône correspondante
holder.iconeView.setImageResource(document.getIcone());
return rowView;
}
|
La cuisson
- notre activité principale
- gère une ListView , une vue qui affiche une liste défilante d’items
- gère les actions clic qui se produisent sur les items.
- La vue ListView demande à l’adaptateur qui lui est associé de lui fournir la vue correspondante pour chaque item visible à l’écran via la méthode getView().
- pour définir un layout personnalisé pour chaque ligne, nous avons construit un adaptateur personnalisé, et redéfinit la méthode getView().
- Il est important d’optimiser la méthode getView() :
- utilisation du système de recyclage des vues fourni par Android.
- mise en cache des vues enfants pour ne pas abuser des findViewById
Et voilà, c’est prêt.
La dégustation
Juste encore une petite modification avant la dégustation :
je vous propose de modifier le point d’entrée dans l’application : la classe Main, lancée lors de l’exécution sera une nouvelle activité qui se contentera de nous rediriger vers ListDocumentsActivity. Comme ça nous allons réviser le lancement d’une autre activité.
Pour faire simple :
- Créez la classe MainActivity dans le package fr.scherrda.android.tuto;
- Lancez un Intent vers ListDocumentsActivity dans onCreate
//Classe MainActivity package fr.scherrda.android.tuto; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); Intent intent = new Intent(getApplicationContext(), ListDocumentsActivity.class); startActivity(intent); } } |
- Mettez à jour le manifest : Déclarez nos 2 activités. Attention, MainActivity est la classe main, lancée à l’exécution
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="fr.scherrda.android.tuto" android:versionCode="1" android:versionName="1.0" android:installLocation="auto"> <uses-sdk android:minSdkVersion="8" /> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name="fr.scherrda.android.tuto.MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".interactivity.LoginActivity"android:label="tuto inter-activité"></activity> <activity android:name="fr.scherrda.android.tuto.interactivity.ShowActivity"></activity> <activity android:name="fr.scherrda.android.tuto.list.ListDocumentsActivity" android:label="Liste personnalisée"> </activity> </application> </manifest> |
j'ai essayé votre code mais ça n'a pas marché, la liste ne s'affiche pas. Je n'arrive pas à voir d'ou vient le problème puisque j'ai tout révisé pas mal de fois.
RépondreSupprimerMerci pour ce tuto, très bien fait ! Adil pour ton soucis, redéfinit la méthode getCount() dans ArrayDocumentAdapter :
Supprimer@Override
public int getCount(){
return nbElementDeTaList; // ici = 4
}
Bonjour,
SupprimerVous pouvez récupérer les sources sur Github.
@Adil : Sans info supplémentaires, difficile de dire. Avez-vous trouvé le problème ? N'avez-vous aucun message d'erreur dans la console ?
Bonjour, je m'excuse d'abord pour le retard de répondre, j'étais en voyage et je ne me connectais pas :), et merci pour vos réponses. Concernant le problème que j'ai rencontré, il était lié à la redéfinition de la méthode getCount(). Vous avez raison GuidedByYou, merci pour votre aide.
RépondreSupprimerMerci pour ce tuto Dahlia, ça m'a aidé à bien comprendre les listes personnalisées et la fonction des adaptateurs.
Si vous voulez bien GuidedByYou, une petite explication serait la bienvenue.
Ce commentaire a été supprimé par l'auteur.
RépondreSupprimerBonjour,
RépondreSupprimerJ'ai suivi votre tuto (et en passant, merci beaucoup). toutefois, j'ai uneanomalie que je ne parvient pas à expliquer :
- le champs "size" ne s'affiche pas dans la liste, seulement le nom du fichier et l'icone.
Merci pour votre aide.
bonjour
RépondreSupprimermerci pour ce tuto c'est le plus clair que j'ai trouvé sur le net
et ca marche ...
Bonjour !
RépondreSupprimerC'est un super tuto. Merci beaucoup
C'est super bien fait je viens de le découvrir et je le trouve cool !!!!
RépondreSupprimerBonjour,
RépondreSupprimerTuto parfait, propre et clair. Tout ce que j'aime quand je cherche une solution sur internet.
Merci !
Bonjour,
RépondreSupprimerMerci pour ce tuto qui m'a bien aidé pour créer une listview.
Par contre j'ai un petit soucis, lorsque je suis sur ma liste et que j'appuie sur le bouton retour du téléphone, j'accède à une page vierge. Je dois appuyer de nouveau sur le bouton retour du téléphone pour revenir à mon activité précédente.
Comment cela se fait-il ?
Merci
good
RépondreSupprimer