Comment fonctionne le mécanisme de recyclage de ListView


146

J'ai donc ce problème que j'avais avant, et naturellement j'ai demandé de l'aide ici . La réponse de Luksprog était excellente car je n'avais aucune idée de la façon dont ListView et GridView s'optimisaient avec le recyclage des vues. Ainsi, avec ses conseils, j'ai pu changer la façon dont j'ai ajouté des vues à mon GridView. Le problème est maintenant que j'ai quelque chose qui n'a pas de sens. C'est mon getViewde mon BaseAdapter:


public View getView(int position, View convertView, ViewGroup parent) {
        if(convertView == null) {
            LayoutInflater inflater = LayoutInflater.from(parent.getContext());
            convertView = inflater.inflate(R.layout.day_view_item, parent, false);
        }
        Log.d("DayViewActivity", "Position is: "+position);
        ((TextView)convertView.findViewById(R.id.day_hour_side)).setText(array[position]);
        LinearLayout layout = (LinearLayout)convertView.findViewById(R.id.day_event_layout);

        //layout.addView(new EventFrame(parent.getContext()));

        TextView create = new TextView(DayViewActivity.this);
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 62, getResources().getDisplayMetrics()), 1.0f);
        params.topMargin = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, getResources().getDisplayMetrics());
        params.bottomMargin = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, getResources().getDisplayMetrics());
        create.setLayoutParams(params);
        create.setBackgroundColor(Color.BLUE);
        create.setText("Test"); 
        //the following is my original LinearLayout.LayoutParams for correctly setting the TextView Height
        //new LinearLayout.LayoutParams(0, (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 60, getResources().getDisplayMetrics()), 1.0f)   
        if(position == 0) {
            Log.d("DayViewActivity", "This should only be running when position is 0. The position is: "+position);
            layout.addView(create);
        }

        return convertView;
    }

}

Le problème est que lorsque je fais défiler, cela se produit, et pas sur la position 0 ... On dirait la position 6 et la position 8, plus cela en met deux en position 8. Maintenant, j'essaie toujours de maîtriser l'utilisation de ListView et de GridView, alors je le fais pas comprendre pourquoi cela se produit. L'une des principales raisons pour lesquelles je pose cette question est d'aider d'autres personnes qui ne connaissent probablement pas la vue de recyclage de ListView et de GridView, ou la façon dont cet article le dit, le mécanisme ScrapView.

entrez la description de l'image ici

Modifier plus tard

Ajouter un lien vers une discussion Google IO qui est essentiellement tout ce dont vous avez besoin pour comprendre le fonctionnement de ListView. Link était mort dans l'un des commentaires. Donc user3427079 était assez gentil pour mettre à jour ce lien. Ici, c'est pour un accès facile.


Haha, merci pour ce lien. J'ai commencé à le regarder maintenant :)
Andy

Vous n'avez pas entièrement implémenté l'exemple de code de la question précédente. L'idée est que vous devez toujours avoir un morceau de code pour annuler les modifications que vous avez apportées aux autres lignes (dans votre cas, la position 0).
Luksprog

Eh bien, je comprends que @Luksprog Mais cela explique-t-il pourquoi il met également la vue aux autres positions? Si quoi que ce soit, je m'attendais à ce que seule la position 0 ait des vues en double. Idk, ayant toujours du mal à comprendre comment ListView lie ce truc: /
Andy

1
Le recyclage signifie que la vue de ligne qui vient de disparaître (par exemple la position 0, après avoir fait défiler une ligne vers le bas) de l'écran peut être utilisée plus tard lorsque le a ListViewbesoin d'une nouvelle ligne à afficher (comme continuer à faire défiler vers le bas / vers le haut). Le problème est que la vue qui vient de disparaître et qui sera utilisée aux futurs postes nécessaires a TextViewdéjà ajouté, c'est le problème. Pour le résoudre, vous devez le supprimer dans la getViewméthode si la getViewméthode est appelée pour une autre position que 0.
Luksprog

2
Le lien de l'article supérieur est mort, je pense que c'est ça? youtube.com/watch?v=N6YdwzAvwOA
G_V

Réponses:


330

Au départ, je n'étais pas non plus au courant du recyclage de listview et du mécanisme d'utilisation de convertview, mais après une journée entière de recherche, je comprends assez bien les mécanismes de la vue liste en faisant référence à une image d' android.amberfog entrez la description de l'image ici

Chaque fois que votre vue de liste est remplie par un adaptateur, cela montre essentiellement le nombre de lignes que la vue de liste peut afficher à l'écran et le nombre de lignes n'augmente pas même lorsque vous faites défiler la liste. C'est l'astuce qu'utilise Android pour que la vue de liste fonctionne plus efficacement et plus rapidement. Maintenant, l'histoire interne de listview se référant à l'image, comme vous pouvez le voir, initialement la listview a 7 éléments visibles, puis, si vous faites défiler vers le haut jusqu'à ce que l'élément 1 ne soit plus visible, getView () passe cette vue (c'est-à-dire élément1) au recycleur et vous pouvez utiliser

System.out.println("getview:"+position+" "+convertView);

à l'intérieur de votre

public View getView(final int position, View convertView, ViewGroup parent)
{
    System.out.println("getview:"+position+" "+convertView);
    ViewHolder holder;
    View row=convertView;
    if(row==null)
    {
        LayoutInflater inflater=((Activity)context).getLayoutInflater();
        row=inflater.inflate(layoutResourceId, parent,false);
        
        holder=new PakistaniDrama();
        holder.tvDramaName=(TextView)row.findViewById(R.id.dramaName);
        holder.cbCheck=(CheckBox)row.findViewById(R.id.checkBox);
        
        row.setTag(holder);
        
    }
    else
    {
        holder=(PakistaniDrama)row.getTag();
    }
            holder.tvDramaName.setText(dramaList.get(position).getDramaName());
    holder.cbCheck.setChecked(checks.get(position));
            return row;
    }

Vous remarquerez dans votre logcat, initialement, convertview est nul pour toutes les lignes visibles, car initialement il n'y avait pas de vues (c'est-à-dire d'éléments) dans le recycleur, donc votre getView () crée une nouvelle vue pour chacun des éléments visibles, mais le dès que vous faites défiler vers le haut et que l'élément 1 sort de l'écran, il sera envoyé au recycleur avec son état actuel (par exemple le `` texte '' TextView ou dans le mien, si la case est cochée, il sera associé à la vue et stocké dans un recycleur).

Maintenant, lorsque vous faites défiler vers le haut / bas, votre liste ne va pas créer une nouvelle vue, elle utilisera la vue qui est dans votre recycleur. Dans votre Logcat, vous remarquerez que le 'convertView' n'est pas nul, c'est parce que votre nouvel élément 8 sera dessiné à l'aide de convertview, c'est-à-dire qu'il prend essentiellement la vue de l'élément 1 du recycleur et gonfle l'élément 8 à sa place, et vous pouvez observer cela dans mon code. Si vous aviez une case à cocher et si vous la cochez à la position 0 (disons que l'élément 1 avait une case à cocher et que vous l'avez cochée) alors lorsque vous faites défiler vers le bas, vous verrez la case à cocher de l'élément 8 déjà cochée, c'est pourquoi la vue de liste utilise la même vue, ne pas créer de nouveau pour vous en raison de l'optimisation des performances.

Choses importantes

1 . Ne réglez jamais le layout_heightet layout_widthde votre ListView wrap_contentcomme getView()forcera votre adaptateur pour obtenir des enfants pour mesurer la hauteur des vues à tirer en vue de la liste et peut entraîner des comportements inattendus comme un retour convertview même la liste n'est pas scrolled.always utiliser match_parentou fixes largeur hauteur.

2 . Si vous souhaitez utiliser une mise en page ou vue après l' affichage de votre liste et pourrait question est venu dans votre esprit si je mets le layout_heightà fill_parentla vue après vue de la liste ne sera pas affiché comme il descend l'écran, il est donc préférable de mettre votre listview dans un Par exemple, mise en page linéaire et définissez la hauteur et la largeur de cette mise en page en fonction de vos besoins et définissez l' attribut de hauteur et de largeur de votre vue de liste à partir de votre mise en page (comme si votre largeur de mise en page est de 320 et la hauteur de 280), alors votre vue de liste devrait avoir la même hauteur et la même largeur. Cela indiquera à getView () la hauteur et la largeur exactes des vues à rendre, et getView () n'appellera pas encore et encore des lignes aléatoires, et d'autres problèmes comme le retour de la vue de conversion avant même que le défilement ne se produise, j'ai un test ceci moi-même, à moins que ma listview ne soit à l'intérieur de lineaLayout, cela posait également des problèmes tels que la répétition de l'appel de vue et la conversion de la vue en tant que, mettre Listview à l'intérieur de LinearLayout fonctionnait comme par magie pour moi (je ne savais pas pourquoi)

01-01 14:49:36.606: I/System.out(13871): getview 0 null
01-01 14:49:36.636: I/System.out(13871): getview 0 android.widget.RelativeLayout@406082c0
01-01 14:49:36.636: I/System.out(13871): getview 1 android.widget.RelativeLayout@406082c0
01-01 14:49:36.646: I/System.out(13871): getview 2 android.widget.RelativeLayout@406082c0
01-01 14:49:36.646: I/System.out(13871): getview 3 android.widget.RelativeLayout@406082c0
01-01 14:49:36.656: I/System.out(13871): getview 4 android.widget.RelativeLayout@406082c0
01-01 14:49:36.666: I/System.out(13871): getview 5 android.widget.RelativeLayout@406082c0
01-01 14:49:36.666: I/System.out(13871): getview 0 android.widget.RelativeLayout@406082c0
01-01 14:49:36.696: I/System.out(13871): getview 0 android.widget.RelativeLayout@406082c0
01-01 14:49:36.706: I/System.out(13871): getview 1 null
01-01 14:49:36.736: I/System.out(13871): getview 2 null
01-01 14:49:36.756: I/System.out(13871): getview 3 null
01-01 14:49:36.776: I/System.out(13871): getview 4 null

Mais maintenant c'est résolu, je sais, je ne suis pas très doué pour expliquer, mais comme j'ai mis toute ma journée à comprendre, j'ai donc pensé que d'autres débutants comme moi peuvent obtenir de l'aide de mon expérience et j'espère que maintenant vous aurez un peu de compréhension de ListView cadre comment cela fonctionne, car il est vraiment en désordre et les débutants tricky donc trouvé trop de problème comprendre


36
"Je sais que je ne suis pas très doué pour expliquer" >>> Les +37 votes sur cette réponse me disent que vous avez fait un excellent travail d'explication. Continuez à partager vos connaissances! ;)
2Dee le

1
Bonne réponse raja ji +1 de moi .. :)
Ehsan Sajjad

2
Merci pour l'image. Après avoir eu beaucoup de problèmes avec ListView lorsque j'ai commencé avec Android, je connaissais déjà le principe du recyclage après avoir tant lutté avec lui. Pourtant, il a pris cette photo et la phrase: if you had a checkbox and if you check it at position 0(let say item1 has also a checkbox and you checked it) so when you scroll down you will see item 8 checkbox is already checked,this is why listview is re using the same view not creating a new for you due to performance optimization.pour que je réalise mon erreur. Meilleur article sur le recyclage Android ListView que j'ai rencontré!
Kevin Cruijssen

1
@MuhammadBabar Oui, j'ai posté Question: stackoverflow.com/q/30122027/1318946
Pratik Butani

1
@MuhammadBabar homme d'explication génial! Cela me fait gagner du temps. Je veux juste savoir, fonctionne-t-il RecyclerViewégalement sur le même mécanisme? Dans ma question stackoverflow.com/questions/47897770/… , je sais que la tâche est impossible avec listView. Dois-je essayer avec recyclerView?
Shambhu

8

Attention, dans le modèle Holder, si vous définissez la position dans votre objet Holder, vous devez la définir à chaque fois, par exemple:

@Override
public final View getView(int position, View view, ViewGroup parent) {
    Holder holder = null;
    if (view == null) {
        LayoutInflater inflater = (LayoutInflater) App.getContext()
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        view = inflater.inflate(getContainerView(), parent, false);
        holder = getHolder(position, view, parent);
        holder.setTag(tag);
        view.setTag(holder);
    } else {
        holder = (Holder) view.getTag();
    }
    holder.position = position;
    draw(holder);
    return holder.getView();
}

ceci est un exemple d'une classe abstraite, où

getHolder(position, view, parent);

effectue toutes les opérations de réglage pour

ImageViews, TextViews, etc..

1
Je n'ai pas remarqué que tout ce temps que je faisais. parent.setTag(holder);au lieu de la manière correcte qui est,view.setTag(holder);
ArtiomLK

2

En utilisant le modèle Holder, vous pouvez obtenir ce que vous voulez:

Vous pouvez trouver la description de ce modèle ici:

Le recyclage de la vue de liste se produit lorsque vous faites défiler l'écran vers le bas et que les éléments de la vue de liste ci-dessus sont masqués. Ils sont réutilisés pour afficher de nouveaux éléments de vue de liste.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.