Optimisations de vos ListViews : partie 1 (Utilisation d’un ViewHolder)
Les listviews sont un passage obligé lorsqu’on fait un tant soit peu de développement Android. Il est cependant compliqué d’en comprendre toutes les spécificités lorsqu’on débute sur la plateforme. Cette série de posts a pour but de vous donner des astuces si vous souhaitez les optimiser afin de les rendre plus fluides.
Ce premier post s’adresse aux débutants et traite d’une technique largement connue, si bien qu’on ne parle plus là d’optimisation mais d’une liste « normale ». Le ViewHolder est une technique indispensable si vous souhaitez avoir une liste digne de ce nom. Mais il faut tout d’abord comprendre leur fonctionnement.
Une liste est composée d’éléments qui sont affichés dans des vues, elles-mêmes déclarées en xml (la plupart du temps). En général, une liste est composée d’éléments dont les vues sont identiques, seul le contenu change. Ainsi, on utilise le même fichier xml pour instancier toutes nos vues. C’est l’adapter qui va jouer le rôle de « fournisseur de vues » à la liste. C’est à lui de créer les vues et de modifier leur contenu. Voici un exemple d’adapter personnalisé basique, affichant des éléments contenant un nom et une description :
public class MyAdapter extends BaseAdapter {
private LayoutInflater mLayoutInflater;
private List mData;
public MyAdapter(Context context, List data){
mData = data;
mLayoutInflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
return mData == null ? 0 : mData.size();
}
@Override
public MyPojo getItem(int position) {
return mData.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View view, ViewGroup viewGroup) {
//This is bad, do not do this at home
View vi = mLayoutInflater.inflate(R.layout.item, null);
TextView tvName = (TextView) vi.findViewById(R.id.textView_item_name);
TextView tvDescription = (TextView) vi.findViewById(R.id.textView_item_description);
MyPojo item = getItem(position);
tvName.setText(item.getName());
tvDescription.setText(item.getDescription());
return vi;
}
}Voila ce qu’un premier jet pourrait donner lorsqu’on créé son adapter. Il faut cependant savoir qu’une ListView recycle ses vues, ce qui permet de ne pas les réinstancier à chaque fois. Par exemple, si on scroll une liste vers le bas, ce sont les mêmes vues qui disparaissent en haut qui vont être réutilisées en bas. Nous allons donc « taguer » nos vues pour les réutiliser et ne pas inflater a chaque getView (ce qui est très gourmand !).
Pour cela, on va créer une classe (appelée ViewHolder par convention) dans laquelle les références vers les différents composants de la vues vont être sauvegardées :
static class ViewHolder{
TextView tvName;
TextView tvDescription;
}Voici le code de l’adapter modifié en conséquence :
@Override
public View getView(int position, View view, ViewGroup viewGroup) {
View vi = view; //trying to reuse a recycled view
ViewHolder holder = null;
if (vi == null) {
//The view is not a recycled one: we have to inflate
vi = mLayoutInflater.inflate(R.layout.item, null);
holder = new ViewHolder();
holder.tvName = (TextView) vi.findViewById(R.id.textView_item_name);
holder.tvDescription = (TextView) vi.findViewById(R.id.textView_item_description);
vi.setTag(holder);
} else {
// View recycled !
// no need to inflate
// no need to findViews by id
holder = (ViewHolder) vi.getTag();
}
MyPojo item = getItem(position);
holder.tvName.setText(item.getName());
holder.tvDescription.setText(item.getDescription());
return vi;
}Si vous souhaitez ajouter des champs comme par exemple une icone dans vos éléments, il faudra bien évidemment également le rajouter dans le ViewHolder.
C’est tout pour ce post, je parlerai probablement la prochaine fois de la manière dont vous pouvez optimiser le chargement de vos images dans une liste (LRUCache, asyncTasks, requestLayout, etc.)