web-dev-qa-db-fra.com

Quel est l'avantage du modèle ViewHolder dans Android?

Lorsque vous développez un Android; et que vous voulez avoir un ArrayAdapter vous pouvez simplement avoir une classe (la plupart du temps avec ViewHolder suffixe) ou gonflez directement votre convertView et trouvez votre vue par id.
Alors, quel est l'avantage d'utiliser ViewHolder?
L'exemple des deux ici:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
        convertView = ((Activity)getContext()).getLayoutInflater().inflate(R.layout.row_phrase, null);
    }
    ((TextView) convertView.findViewById(R.id.txtPhrase)).setText("Phrase 01");
}

Ou créez une classe interne dans ArrayAdapter comme suit:

static class ViewHolder {   
    ImageView leftIcon;   
    TextView upperLabel;  
    TextView lowerLabel;  
}

et enfin dans le getView:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder holder = null;
    if (view == null) {
        view = LayoutInflater.from(context).inflate(R.layout.row_layout,
                    null, false);
        holder = new ViewHolder();
        holder.leftIcon = (ImageView) view.findViewById(R.id.leftIcon);
    }
}
36
mohammad jannesary

Comprendre le fonctionnement du recyclage listview

Fonctionnement du mécanisme de recyclage de ListView

Vous ne pouvez pas recycler une ligne actuellement utilisée. Le lien ci-dessus explique le fonctionnement du mécanisme de recyclage listview

Alors, quel est l'avantage d'utiliser ViewHolder?

Citation de documents

Votre code peut appeler findViewById() fréquemment pendant le défilement de ListView, ce qui peut ralentir les performances. Même lorsque l'adaptateur renvoie une vue gonflée pour recyclage, vous devez toujours rechercher les éléments et les mettre à jour. Une façon de contourner l'utilisation répétée de findViewById() consiste à utiliser le modèle de conception "vue titulaire".

    public View getView(int position, View convertView, ViewGroup parent) { 
             ViewHolder holder; 

             if (convertView == null) { // if convertView is null
                 convertView = mInflater.inflate(R.layout.mylayout, 
                         parent, false);
                 holder = new ViewHolder(); 
                     // initialize views  
                convertView.setTag(holder);  // set tag on view
            } else { 
                holder = (ViewHolder) convertView.getTag();
                        // if not null get tag 
                        // no need to initialize
            } 

            //update views here  
            return convertView; 
    }

Vous avez manqué la partie importante convertView.setTag(holder) et holder = (ViewHolder) ConvertView.getTag()

http://developer.Android.com/training/improving-layouts/smooth-scrolling.html

34
Raghunandan

Lorsque vous parcourez votre ListView, il n'y a qu'une poignée de vues affichées à un moment donné. Cela signifie que vous n'avez pas besoin d'instancier une vue pour chaque élément de votre adaptateur; quand une vue défile hors écran, elle peut être réutilisée, ou recyclée.

Le recyclage des vues et le modèle ViewHolder ne sont pas identiques. Le modèle ViewHolder est uniquement destiné à réduire le nombre d'appels view.findViewById(int) que vous effectuez. Le modèle ViewHolder ne fonctionne que lorsque vous profitez du recyclage des vues.

Dans getView(int position, View convertView, ViewGroup parent), le paramètre convertView est soit nul, soit c'est une vue qui a été recyclée: elle sera toujours avoir les données d'un élément de liste différent lié à lui.

Sans le modèle ViewHolder, vous pouvez toujours profiter du recyclage des vues (c'est-à-dire ne pas instancier les vues à l'aveugle):

public View getView(int position, View convertView, ViewGroup parent) {
  View view = convertView;
  if (view == null) {
    view = // inflate new view
  }

  ImageView imageView = (ImageView) view.findViewById(R.id.listitem_image);
  TextView textView = (TextView) view.findViewById(R.id.listitem_text);
  TextView timestampView = (TextView) view.findViewById(R.id.listitem_timestamp);
  ProgressBar progressSpinnerView = (ProgressBar) view.findViewById(R.id.progress_spinner);

  // TODO: set correct data for this list item
  // imageView.setImageDrawable(...)
  // textView.setText(...)
  // timestampView.setText(...)
  // progressSpinnerView.setProgress(...)

  return view;
}

Ci-dessus est un exemple de recyclage des vues - nous ne gonflons pas une nouvelle vue pour chaque ligne; nous ne gonflons une vue que si on ne nous en donne pas une à réutiliser. Éviter d'avoir à gonfler une vue est la partie qui contribuera certainement aux performances lors du défilement de votre liste: profitez du recyclage des vues.

Alors, à quoi sert le ViewHolder? Nous faisons actuellement 4x findViewById(int) pour chaque élément, que la ligne elle-même existe déjà ou non. Comme findViewById(int) itère récursivement un ViewGroup jusqu'à ce qu'il trouve un descendant avec l'ID donné, cela est un peu inutile pour nos vues recyclées - nous retrouvons des vues auxquelles nous avons déjà des références.

Évitez cela en utilisant un objet ViewHolder pour conserver les références aux sous-vues après les avoir "trouvées":

private static class ViewHolder {
  final TextView text;
  final TextView timestamp;
  final ImageView icon;
  final ProgressBar progress;

  ViewHolder(TextView text, TextView timestamp, ImageView icon, ProgressBar progress) {
    this.text = text;
    this.timestamp = timestamp;
    this.icon = icon;
    this.progress = progress;
  }
}

View.setTag(Object) vous permet de dire à la vue de contenir un objet arbitraire. Si nous l'utilisons pour contenir une instance de notre ViewHolder après avoir fait nos appels findViewById(int), nous pouvons utiliser View.getTag() sur des vues recyclées pour éviter d'avoir à faire les appels encore et encore.

public View getView(int position, View convertView, ViewGroup parent) {
  View view = convertView;
  if (view == null) {
    view = // inflate new view
    ViewHolder holder = createViewHolderFrom(view);
    view.setTag(holder);  
  }
  ViewHolder holder = view.getTag();
  // TODO: set correct data for this list item
  // holder.icon.setImageDrawable(...)
  // holder.text.setText(...)
  // holder.timestamp.setText(...)
  // holder.progress.setProgress(...)
  return view;
}

private ViewHolder createViewHolderFrom(View view) {
    ImageView icon = (ImageView) view.findViewById(R.id.listitem_image);
    TextView text = (TextView) view.findViewById(R.id.listitem_text);
    TextView timestamp = (TextView) view.findViewById(R.id.listitem_timestamp);
    ProgressBar progress = (ProgressBar) view.findViewById(R.id.progress_spinner);

    return new ViewHolder(text, timestamp, icon, progress);
}

Les avantages en termes de performances de cette optimisation sont discutables , mais c'est le avantage du ViewHolder .

17
ataulm

ViewHolder le modèle de conception est utilisé pour accélérer le rendu de votre ListView - en fait pour le faire fonctionner sans problème, findViewById est assez cher (il fait l'analyse DOM) lorsqu'il est utilisé chaque fois qu'un élément de liste est rendu, il doit traverser votre hiérarchie de mise en page et aussi instancier des objets. Étant donné que les listes peuvent redessiner ses éléments assez fréquemment pendant le défilement, ces frais généraux peuvent être importants.

vous pouvez trouver une bonne explication de comment cela fonctionne dans:

http://www.youtube.com/watch?v=wDBM6wVEO70&feature=youtu.be&t=7m

à partir de la minute 10, vous avez expliqué le modèle de conception de ViewHolder par des experts Google.

[modifier]

findViewById n'instancie pas de nouveaux objets, il traverse uniquement la hiérarchie - voici la référence http://androidxref.com/5.1.1_r6/xref/frameworks/base/core/Java/Android/view/ViewGroup.Java#361

14
marcinj

D'abord :

Dans ListView lorsque vous faites défiler le ListView, vous devez créer un nouvel élément et lier ses données dessus, donc si vous avez beaucoup d'éléments dans ListView, cela peut provoquer une fuite de mémoire car plus d'objets vous avez créé des éléments, mais Android utilisant le concept de recyclage de la plupart de ses API, et cela signifie que vous créez un objet et que vous l'utilisez au lieu de le détruire et d'en déclarer un nouveau, donc lorsque vous faites défiler ListView API utilisant les éléments invisibles que vous avez fait défiler et les transmettre pour vous dans la méthode getView c'est convertView donc ici vous traitez avec plus d'éléments ListView

Deuxièmement :

si vous avez un élément personnalisé dans ListView, vous devez joindre votre mise en page personnalisée à chaque élément du ListView afin que chaque fois que ListView lie un nouvel élément à l'aide de findViewById pour obtenir la référence des éléments de mise en page. Cette méthode ira à la recherche de votre élément de manière récursive afin que vous ViewHolder vous aidiez à faire le récursif une seule fois, puis il contiendra la référence de l'élément de mise en page pour vous jusqu'à ce que vous puissiez le joindre pour ListView

j'espère que cela vous aide et me nourrit de toute chose pas évidente

3
mohammed momn