Funktionsweise des Recyclingmechanismus von ListView

146

Also habe ich dieses Problem hatte ich vor, und natürlich bat ich um Hilfe zu hier . Die Antwort von Luksprog war großartig, da ich keine Ahnung hatte, wie sich ListView und GridView durch das Recycling von Views optimiert haben. Mit seinem Rat konnte ich also ändern, wie ich meinem GridView Ansichten hinzufügte. Das Problem ist jetzt, dass ich etwas habe, das keinen Sinn ergibt. Das ist mein getViewvon meinem 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;
    }

}

Das Problem ist, wenn ich scrolle, passiert dies und nicht auf Position 0 ... Sieht aus wie Position 6 und Position 8, und es bringt zwei auf Position 8. Jetzt versuche ich immer noch, den Dreh raus zu bekommen, ListView und GridView zu verwenden, so wie ich es tue Ich verstehe nicht, warum das passiert. Einer der Hauptgründe, warum ich diese Frage stelle, besteht darin, anderen zu helfen, die wahrscheinlich nichts über ListView und GridViews Recycling-Ansicht oder den ScrapView-Mechanismus in diesem Artikel wissen.

Geben Sie hier die Bildbeschreibung ein

Später bearbeiten

Hinzufügen eines Links zu einem Google IO-Vortrag, der im Grunde alles ist, was Sie brauchen, um zu verstehen, wie ListView funktioniert. Link war in einem der Kommentare tot. User3427079 war also nett genug, um diesen Link zu aktualisieren. Hier ist es für den einfachen Zugang.

Andy
quelle
Haha, danke für diesen Link. Begann es jetzt zu sehen :)
Andy
Sie haben das Codebeispiel aus der vorherigen Frage nicht vollständig implementiert. Die Idee ist, dass Sie immer einen Code benötigen, um die Änderungen, die Sie für andere Zeilen vorgenommen haben (in Ihrem Fall die Position 0) , rückgängig zu machen .
Luksprog
Nun, ich verstehe das @Luksprog. Aber erklärt das, warum es die Ansicht auch an den anderen Positionen platziert? Wenn überhaupt, habe ich erwartet, dass nur Position 0 doppelte Ansichten enthält. Idk, immer noch Probleme zu verstehen, wie ListView dieses Zeug bindet: /
Andy
1
Recycling bedeutet, dass die Zeilenansicht, die gerade vom Bildschirm verschwunden ist (z. B. Position 0, nachdem eine Zeile nach unten gescrollt wurde), später verwendet werden kann, wenn ListVieweine neue Zeile angezeigt werden muss (z. B. weiter nach unten / oben scrollen). Das Problem ist, dass die Ansicht, die gerade verschwunden ist und an zukünftigen benötigten Positionen verwendet wird, TextViewbereits hinzugefügt wurde. Dies ist das Problem. Um es zu lösen, müssen Sie es in der getViewMethode entfernen , wenn die getViewMethode für eine andere Position als aufgerufen wird 0.
Luksprog
2
Der Link des Top-Beitrags ist tot. Ich denke, das ist es? youtube.com/watch?v=N6YdwzAvwOA
G_V

Antworten:

330

Anfangs war mir auch das Recycling von Listenansichten und der Mechanismus zur Verwendung von Konvertierungsansichten nicht bekannt, aber nach ein paar Tagen Recherche verstehe ich die Mechanismen der Listenansicht ziemlich gut, indem ich mich auf ein Bild aus android.amberfog beziehe Geben Sie hier die Bildbeschreibung ein

Immer wenn Ihre Listenansicht von einem Adapter gefüllt wird, wird im Grunde die Anzahl der Zeilen angezeigt, die die Listenansicht auf dem Bildschirm anzeigen kann, und die Anzahl der Zeilen erhöht sich nicht, selbst wenn Sie durch die Liste scrollen. Dies ist der Trick, den Android verwendet, damit die Listenansicht effizienter und schneller funktioniert. Wie Sie sehen können, enthält die Listenansicht, die sich auf das Bild bezieht, zunächst 7 sichtbare Elemente. Wenn Sie nach oben scrollen, bis Element 1 nicht mehr sichtbar ist, übergibt getView () diese Ansicht (dh Element1) an das Recycler und Sie können verwenden

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

in deinem

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;
    }

Sie werden in Ihrem Logcat feststellen, dass die Konvertierungsansicht zunächst für alle sichtbaren Zeilen null ist, da im Recycler anfangs keine Ansichten (dh Elemente) vorhanden waren. Daher erstellt getView () für jedes der sichtbaren Elemente eine neue Ansicht, jedoch für die Sobald Sie nach oben scrollen und Element 1 den Bildschirm verlässt, wird es mit seinem aktuellen Status an den Recycler gesendet (z. B. der TextView-Text oder in meinem Fall, wenn das Kontrollkästchen aktiviert ist, wird es der Ansicht und zugeordnet im Recycler gelagert).

Wenn Sie jetzt nach oben / unten scrollen, erstellt Ihre Listenansicht keine neue Ansicht, sondern verwendet die Ansicht in Ihrem Recycler. In Ihrem Logcat werden Sie feststellen, dass die 'convertView' nicht null ist, da Ihr neues Element 8 mit convertview gezeichnet wird, dh im Grunde genommen nimmt es die Ansicht von Element 1 vom Recycler und bläst Element 8 an seiner Stelle auf, und Sie können beobachten das in meinem Code. Wenn Sie ein Kontrollkästchen hatten und es an Position 0 aktiviert haben (sagen wir, Element1 hatte ein Kontrollkästchen und Sie haben es aktiviert). Wenn Sie also nach unten scrollen, wird das Kontrollkästchen für Element 8 bereits aktiviert. Aus diesem Grund verwendet die Listenansicht dieselbe Ansicht. Aufgrund der Leistungsoptimierung wird kein neues für Sie erstellt.

Wichtige Dinge

1 . Stellen Sie das layout_heightund layout_widthIhrer Listenansicht niemals wrap_contentso getView()ein, dass Ihr Adapter gezwungen wird, ein untergeordnetes Element zum Messen der Höhe der in der Listenansicht zu zeichnenden Ansichten zu erhalten. Dies kann zu unerwartetem Verhalten wie der Rückgabe der Konvertierungsansicht führen, auch wenn die Liste nicht gescrollt wird. Immer verwenden match_parentoder korrigieren Breite Höhe.

2 . Wenn Sie ein Layout oder eine Ansicht verwenden möchten, nachdem Ihre Listenansicht und Ihre Frage in den Sinn gekommen sind, wenn ich die Ansicht layout_heightauf fill_parentdie Ansicht eingestellt habe , wird die Listenansicht nicht angezeigt, wenn sie auf dem Bildschirm angezeigt wird. Daher ist es besser, Ihre Listenansicht in eine zu setzen Zum Beispiel Lineares Layout und stellen Sie die Höhe und Breite dieses Layouts gemäß Ihren Anforderungen ein und ändern Sie das Attribut für Höhe und Breite Ihrer Listenansicht ab Ihrem Layout (z. B. wenn Ihre Layoutbreite 320 und die Höhe 280 beträgt ) und dann Ihre Listenansicht sollte die gleiche Höhe und Breite haben. Dies teilt getView () die genaue Höhe und Breite der zu rendernden Ansichten mit, und getView () ruft nicht immer wieder zufällige Zeilen auf, und andere Probleme wie das Zurückgeben der Konvertierungsansicht, noch bevor das Scrollen nicht auftritt, habe ich getestet dies selbst, es sei denn, meine Listenansicht befand sich im lineaLayout, es gab auch Probleme wie das Wiederholen des Aufrufs der Ansicht und das Konvertieren der Ansicht als, das Einfügen der Listenansicht in das LinearLayout funktionierte für mich wie Zauberei (wusste nicht warum)

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

Aber jetzt ist es gelöst, ich weiß, ich kann es nicht so gut erklären, aber da ich meinen ganzen Tag damit verbracht habe, es zu verstehen, dachte ich, dass andere Anfänger wie ich Hilfe von meiner Erfahrung bekommen können und ich hoffe, dass ihr jetzt ein bisschen Verständnis habt des ListView- Frameworks, wie es funktioniert, da es wirklich chaotisch und knifflig ist, so dass Anfänger zu viele Probleme beim Verstehen hatten

Muhammad Babar
quelle
36
"Ich weiß, dass ich nicht so gut erklären kann" >>> Die +37 Stimmen zu dieser Antwort sagen mir, dass Sie hervorragende Arbeit beim Erklären geleistet haben. Bitte teilen Sie Ihr Wissen weiter! ;)
2Dee
1
Tolle Antwort raja ji +1 von mir .. :)
Ehsan Sajjad
2
Danke für das Bild. Nachdem ich viele Probleme mit ListView hatte, als ich mit Android anfing, kannte ich das Recycling-Prinzip bereits, nachdem ich so viel damit zu kämpfen hatte. Trotzdem brauchte es dieses Bild und den Satz: 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.damit ich meinen Fehler erkannte. Bester Beitrag über Android ListView Recycling, auf den ich gestoßen bin!
Kevin Cruijssen
1
@ MuhammadBabar Ja, ich habe Frage gestellt: stackoverflow.com/q/30122027/1318946
Pratik Butani
1
@ MuhammadBabar super Erklärung Mann! Das spart mir Zeit. Ich möchte nur wissen, ob das RecyclerViewauch mit dem gleichen Mechanismus funktioniert. In meiner Frage stackoverflow.com/questions/47897770/… weiß ich , dass die Aufgabe mit ziemlich unmöglich ist listView. Soll ich es versuchen recyclerView?
Shambhu
8

Achten Sie im Holder-Muster darauf, dass Sie die Position in Ihrem Holder-Objekt jedes Mal festlegen, zum Beispiel:

@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();
}

Dies ist ein Beispiel aus einer abstrakten Klasse, in der

getHolder(position, view, parent);

führt alle Einstellvorgänge für die aus

ImageViews, TextViews, etc..
Ahmed Adel Ismail
quelle
1
Ich habe das die ganze Zeit nicht bemerkt. parent.setTag(holder);anstelle des richtigen Weges, der ist,view.setTag(holder);
ArtiomLK
2

Mit dem Halter-Muster können Sie erreichen, was Sie wollen:

Eine Beschreibung dieses Musters finden Sie hier:

Das Recycling der Listenansicht erfolgt, wenn Sie auf dem Bildschirm nach unten scrollen und die obigen Listenansichtselemente ausgeblendet sind. Sie werden wiederverwendet, um neue Listenansichtselemente anzuzeigen.

Krishna
quelle