Cours Android utilisation de convertView

Mieux, plus robuste et plus rapide

L’implémentation de getView() que nous venons de présenter fonctionne, mais elle est peu efficace. En effet, à chaque fois que l’utilisateur fait défiler l’écran, on doit créer tout un lot de nouveaux objets View pour les nouvelles lignes qui s’affichent. Le framework d’Android ne mettant pas automatiquement en cache les objets View existants, il faut en recréer de nouveaux, même pour des lignes que l’on avait créées très peu de temps auparavant. Ce n’est donc pas très efficace, ni du point de vue de l’utilisateur, qui risque de constater que la liste est lente, ni du point de vue de la batterie – chaque action du CPU consomme de l’énergie. Ce traitement supplémentaire est, par ailleurs, aggravé par la charge que l’on impose au ramasse-miettes (garbage collector) puisque celui-ci doit détruire tous les objets que l’on crée. Par conséquent, moins le code est efficace, plus la batterie du téléphone se décharge vite et moins l’utilisateur est content. On doit donc passer par quelques astuces pour éviter ces défauts.

Utilisation de convertView

La méthode getView() reçoit en paramètre un objet View nommé, par convention, convertView. Parfois, cet objet est null, auquel cas vous devez créer une nouvelle View pour la ligne (par inflation), comme nous l’avons expliqué plus haut. Si convertView n’est pas null, en revanche, il s’agit en fait de l’une des View que vous avez déjà créées. Ce sera notamment le cas lorsque l’utilisateur fait défiler la ListView : à mesure que de nouvelles lignes apparaissent, Android tentera de réutiliser les vues des lignes qui ont disparu à l’autre extrémité, vous évitant ainsi de devoir les reconstruire totalement. En supposant que chaque ligne ait la même structure de base, vous pouvez utiliser findViewById() pour accéder aux différents widgets qui composent la ligne, modifier leur contenu, puis renvoyer convertView à partir de getView() au lieu de créer une ligne totalement nouvelle. Voici, par exemple, une écriture optimisée de l’implémentation précédente de getView(), extraite du projet FancyLists/Recycling :

public class RecyclingDemo extends ListActivity { TextView selection; String[] items={« lorem », « ipsum », « dolor », « sit », « amet », « consectetuer », « adipiscing », « elit », « morbi », « vel », « ligula », « vitae », « arcu », « aliquet », « mollis », « etiam », « vel », « erat », « placerat », « ante », « porttitor », « sodales », « pellentesque », « augue », « purus »}; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); setListAdapter(new IconicAdapter(this)); selection=(TextView)findViewById(R.id.selection); } public void onListItemClick(ListView parent, View v, int position, long id) { selection.setText(items[position]); } class IconicAdapter extends ArrayAdapter { Activity context; IconicAdapter(Activity context) { super(context, R.layout.row, items); this.context=context; }
public View getView(int position, View convertView, ViewGroup parent) { View row=convertView; if (row==null) { LayoutInflater inflater=context.getLayoutInflater(); row=inflater.inflate(R.layout.row, null); } TextView label=(TextView)row.findViewById(R.id.label); label.setText(items[position]); ImageView icon=(ImageView)row.findViewById(R.id.icon); if (items[position].length()>4) { icon.setImageResource(R.drawable.delete); } else { icon.setImageResource(R.drawable.ok); } return(row); } } }

Si convertView est null, nous créons une ligne par inflation ; dans le cas contraire, nous nous contentons de la réutiliser. Le code pour remplir les contenus (image de l’icône, texte du label) est identique dans les deux cas. On évite ainsi une étape d’inflation potentiellement coûteuse lorsque convertView n’est pas null. Cependant, cette approche ne fonctionne pas toujours. Si, par exemple, une ListView comprend des lignes ne contenant qu’une seule ligne de texte et d’autres en contenant plusieurs, la réutilisation des lignes existantes devient problématique car les layouts risquent d’être très différents. Si l’on doit créer une View pour une ligne qui compte deux lignes de texte, par exemple, on ne peut pas se contenter de réutiliser une View avec une seule ligne : il faut soit modifier les détails internes de cette View, soit l’ignorer et en créer une nouvelle.Il existe bien entendu des moyens de gérer ce type de problème, comme rendre la seconde ligne de texte visible ou non en fonction des besoins, mais n’oubliez pas que chaque milliseconde d’utilisation du CPU d’un téléphone est précieuse – pour la fluidité de l’interface, mais surtout pour la batterie.Ceci étant dit, surtout si vous débutez avec Android, intéressez-vous d’abord à obtenir la fonctionnalité que vous désirez et essayez ensuite d’optimiser les performances lors d’un second examen de votre code. Ne tentez pas de tout régler d’un coup, sous peine de vous noyer dans un océan de View.

LIRE AUSSI :  Programmation Android base de données relationnelle

Utilisation du patron de conception « support »

L’appel de findViewById() est également coûteux : cette méthode plonge dans les lignes de la liste pour en extraire les widgets en fonction de leurs identifiants, afin que l’on puisse en personnaliser le contenu (pour modifier le texte d’un TextView, changer l’icône d’un ImageView, par exemple). findViewById() pouvant trouver n’importe quel widget dans l’arbre des fils de la View racine de la ligne, cet appel peut demander un certain nombre d’instructions pour s’exécuter, notamment si l’on doit retrouver à nouveau des widgets que l’on a déjà trouvés auparavant. Certains kits de développement graphiques évitent ce problème en déclarant les View composites, comme nos lignes, dans le code du programme (en Java, ici). L’accès aux différents widgets ne consiste plus, alors, qu’à appeler une méthode d’accès ou à lire un champ. Nous pourrions bien sûr faire de même avec Android, mais cela alourdirait le code. Nous préférons trouver un moyen de continuer à utiliser le fichier de description XML tout en mettant en cache les widgets fils essentiels de notre ligne, afin de ne devoir les rechercher qu’une seule fois.
C’est là qu’entre en jeu le patron de conception « support », qui est implémenté par une classe que nous appellerons ViewWrapper. Tous les objets View disposent des méthodes getTag() et setTag(), qui permettent d’associer un objet quelconque au widget. Le patron « support » utilise ce « marqueur » pour détenir un objet qui, à son tour, détient chaque widget fils intéressant. En attachant le support à l’objet View de la ligne, on a accès immédiatement aux widgets fils qui nous intéressent à chaque fois que l’on utilise cette ligne, sans devoir appeler à nouveau findViewById(). Examinons l’une de ces classes support (extrait du projet FancyLists/ViewWrapper) :

class ViewWrapper { View base; TextView label=null; ImageView icon=null; ViewWrapper(View base) { this.base=base; } TextView getLabel() { if (label==null) { label=(TextView)base.findViewById(R.id.label); }return(label); } ImageView getIcon() { if (icon==null) { icon=(ImageView)base.findViewById(R.id.icon); } return(icon); } }

ViewWrapper ne détient pas seulement les widgets fils : elle les recherche uniquement si elle ne les détient pas déjà. Si vous créez un wrapper et que vous n’ayez jamais besoin d’un fils précis, il n’y aura donc jamais aucun appel de findViewById() pour le retrouver et vous n’aurez jamais à payer le prix de ces cycles CPU inutiles.
Le patron « support » permet également d’effectuer les traitements suivants :
● Il regroupe au même endroit le transtypage de tous nos widgets, au lieu de le disséminer dans chaque appel à findViewById().
● Il permet de mémoriser d’autres informations sur les lignes, comme leur état, que nous ne voulons pas insérer dans le modèle sous-jacent. L’utilisation de ViewWrapper consiste simplement à créer une instance de cette classe à chaque fois que l’on crée une ligne par inflation et à attacher cette instance à la vue de la ligne via setTag(), comme dans cette version de getView() :

public class ViewWrapperDemo extends ListActivity { TextView selection; String[] items={« lorem », « ipsum », « dolor », « sit », « amet », « consectetuer », « adipiscing », « elit », « morbi », « vel », « ligula », « vitae », « arcu », « aliquet », « mollis », « etiam », « vel », « erat », « placerat », « ante », « porttitor », « sodales », « pellentesque », « augue », « purus »}; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); setListAdapter(new IconicAdapter(this)); selection=(TextView)findViewById(R.id.selection); } private String getModel(int position) { return(((IconicAdapter)getListAdapter()).getItem(position)); }

Cours gratuitTélécharger le cours complet

Télécharger aussi :

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *