Gérer des évènements dans une listview.

Mardi 1 mai 2012, 14:49

Bonjour à tous, ceci est mon premier tuto-conseil soyez indulgent :D.

Je vais vous présenter un adapter que j’utilise, assez souvent, dans Androïd. Tout d’abord voici une classe abstraite, qui n’est autre qu’un adapter qui étends BaseAdapter, et qui va nous aider a implémenter, facilement des événements dans notre liste. ClickableListAdapter.java

Je vois parfois des codes qui utilise, pour une liste, le onItemClickListener, donc l’action ne s’arrête qu’au simple click d’une ligne, alors que nous pourrions avoir plusieurs boutons, textview et autres interactivité sur un seul champ.

Personnellement voici ce que j’obtiens, sur une seule ligne :

[]1

La vue de chaque ligne avec leurs évènements propres.

J’ai donc deux actions, une checbkox et une image qui ouvre une quickaction, remplacé dans le tuto par un Toast. J’aurais pu rajouter plus, mais étant limité sur la largeur, au lieu de mettre plusieurs image-button, nous avons opté pour une quickaction qui est un semblant de pop-up.

Cela m’évite de retoucher ma vue et vu que nous rajoutions au fur et à mesure des actions possibles, c’était plus facile. Mais je m’égare, cela sera peut-être un prochain tuto, si vous me le demandez, revenons à nos adapter.

Explications:

Que fait la classe ClickableAdapter, c’est très simple, elle créé la vue de chaque ligne dans la liste, et nous la retourne. Tout ce que vous avez à faire, est d’étendre cette classe, et de définir votre classe Holder qui étendra ViewHolder. La classe Holder, n’est autre que les champs (textview, imageview, checkbox) que vous avez dans votre vue xml, qui défini chaque ligne. Nous créons notre Adapter personnel qui étends ClickableAdapter et nous nous retrouvons avec deux méthodes et un constructeur.

class MyAdapter extends ClickableListAdapter {

    public MyAdapter(Context context, int viewid, List objects) {
        super(context, viewid, objects);
        // TODO Auto−generated constructor stub
    }

    @Override
    protected ViewHolder createHolder(View v) {
        // TODO Auto−generated method stub
        return null;
    }

    @Override
    protected void bindHolder(ViewHolder h) {
        // TODO Auto−generated method stub

    }
}

Je récupère la taille de l’objet passé en paramètre, pour créer un tableau de boolean. Pourquoi me direz-vous, tout simplement parce que j’ai trouvé des petits problèmes de récupération de position dans une liste où nous devons scroller.

Il check des checkbox aléatoirement à la création/recréation de ligne et c’est très embêtant. J’ai trouvé comme solution, de créer un tableau de boolean, qui récupère la position de l’objet de la ligne (un int position dans l’objet bean lui même) et je check alors s’il est à vrai ou faux. À l’action check, il défini la position du tableau à vrai, et donc quand il recréera la ligne, si vous avez scrollé, il redéfinira correctement à vrai le checkbox.

int nbre = objects.size();
checkbox = new boolean[nbre];
for (int i = 0; i < nbre; i++) {
    checkbox[i] = false;
}

La méthode bindHolder, n’est que le remplissage de donnée, dans la vue elle-même. En conséquent, la méthode createHolder, nous créé la vue. Vu qu’elle est créé dynamiquement, toutes les actions seront les mêmes pour tous les lignes, il suffirait de rajouter des if ou un switch qui détecterait la position de l’objet dans la vue, avec la variable position, pour définir des actions différentes dans les évènements.

J’ai rajouté dans le onCheck la petite subtilité qui gère notre tableau de booleans, ainsi qu’une légère animation sur la ligne elle-même.

@Override
 public void onCheck(CompoundButton buttonView, boolean isChecked,
 ViewHolder mViewHolder) {
     RowHolder rh = (RowHolder) mViewHolder;
     br = (BeanRow) rh.data;

     checkbox[br.pos] = buttonView.isChecked();

     if (isChecked) {

         rh.rowAction.setEnabled(!checkbox[br.pos]);
         AlphaAnimation anim = new AlphaAnimation(1, 0.2f);
         anim.setDuration(0);
         anim.setFillAfter(true);
         v.startAnimation(anim);
     } else {

         rh.rowAction.setEnabled(!checkbox[br.pos]);
         AlphaAnimation anim = new AlphaAnimation(0.2f, 1);
         anim.setDuration(0);
         anim.setFillAfter(true);
         v.startAnimation(anim);
     }
}

N’oubliez pas que les onClick/onCheck et onLongClick sont ceux de la classe ClickableListAdapter, et non pas ceux proposé par Androïd, sinon vous ne récupéreriez pas la vue de votre ligne et donc, vous aurez une erreur. Voilà, j’espère avoir été clair. Je vous ai uploadé sur code.google le petit projet tuto, il sera surement plus facile a comprendre :-).

Bonne journée.

A propos de l'auteur
Laurent

Développeur Java & mobile, plus particulièrement sur Android, passionné par les technologies mobile et la S-F, je travail pour VLS Henrotte une entreprise basé à Liège. Autodidacte, motivé et surtout très ouvert à tout ce qui approche de l’innovation ou la révolution, je partage mes connaissances ou mes sources. Je suis aussi un twitterfou

Laisser un commentaire

Commentaires

Quelques remarques:

- Si les cases à cocher ne reviennent pas dans l’état dans lequel on les a laissées, ce n’est pas un problème aléatoire, c’est simplement parce que les vues de la liste sont recyclées. Tout doit être initialisé correctement dans getView().
- Idéalement l’était coché/décoché doit être stocké dans l’objet du modèle lié à la ligne, ce qui permet de se passer d’un tableau de booléens externes. Comme cela dans la méthode getView() de l’adapter, on peut initialiser l’état de la checkbox comme il faut en allant le chercher dans l’objet du modèle.
- On peut stocker l’objet du modèle dans le tag de la vue checkbox dans la méthode getView() de l’adapter (la méthode setTag() est disponible sur toutes les vues). De cette façon, quand on cliquera sur la checkbox, on pourra récupérer l’objet du modèle directement via getTag() sur la vue passée en paramètre dans onCheckedChanged() et le mettre à jour avec le nouvel état coché/décoché.
- Normalement un ViewHolder ne contient que des vues, comme son nom l’indique, et non l’objet du modèle. Il est uniquement utilisé dans la méthode getView() de l’adapter pour récupérer les sous-vues déjà instanciées en cas de recyclage, évitant ainsi de refaire des findViewById() sur la vue parente passée en paramètre. On n’a pas besoin de stocker l’objet du modèle dans le ViewHolder car dans la méthode getView() on peut l’avoir facilement en appelant la méthode getItem(position).

Bonjour,
« les vues de la liste sont recyclées » Je le dis, à ma façon, peut-être pas clair, « à la création/recréation de ligne », pour les booleans dans l’objet, je n’y avais pas réellement fait attention c’est vrai ;-).

Part contre j’ai toujours eu des soucis pour récupérer la bonne position de la liste, même avec les tags (récupération de la dernière ligne créée). Le pourquoi je passe par ce genre de façon de faire. Si cela n’est pas optimisé, je serai heureux d’avoir une autre version qui fait exactement la même chose, pour apprendre et par la même partager.

Bonne journée.

[...] Souvenez-vous, j’avais réalisé et utilisé une classe qui nous gérait nos actions, aujourd’hui, nous utiliserons que le système Androïd et ses propres outils ! [...]

bonjour!!
très bien le tuto!!je suis sur un projet j’aimerais afficher une listview de 150 items mais le probleme se pose sur la longueur de la liste .alors je trouve bien étant donné que se sont des boutiques qui pour certaines ferment a 2Oh et pour d’outres reste ouverte toute la nuit(pour le service de nuit)aussi il y en a qui sont ouverte les week-end(samedi et dimanche)de 7heure a 2OH et d’outres de 20H à 7heure du matin.j’aimerais donc que ma listview prennent en compte ces heures et jours afin de ne pas faire afficher une tres longue liste.j’aimerais seulment afficher une liste des boutique ouvertent en prennant en compte les paramètre des heure et des jour d’ouverture et de fermeture des boutiques.pouvez vous maider?

Alors je définirai déjà des catégories de magasin, pour faciliter la recherche et alléger la liste.
En deuxième temps, ça dépendra de comment tu veux modifier ce genre de donnée.

Tu prends par jour actuel et tu fais une clause ‘where’ dans ta requête sql pour les éviter.

Soit dans ton objet (qui sera dans le ‘holder’), il doit connaitre ses heures d’ouvertures/fermetures et ses jours ouvrables.
Et tu poses des checkbox (ou dans les settings) qui vont activer des filtres dans ton adapter, qui testerons les heures/jours de chaque objet et il s’affichera ou pas dans ta liste.

Je pense que le plus simple reste la requête sql qui évitera les magasins pas ouvert pour le jour/heure.

En espérant t’avoir ouvert une piste ;-)