Ich habe eine Liste mit 13 Elementen (obwohl Elemente hinzugefügt oder entfernt werden können), Positionen 0-12. Wenn das Fragment, das die RecyclerView enthält, zuerst angezeigt wird, sind für den Benutzer nur die Positionen 0 bis 7 sichtbar (Position 7 ist nur zur Hälfte sichtbar). In meinem Adapter Log
jedes Mal, wenn ein Ansichtshalter gebunden/gebunden ist (idk, wenn hier Grammatik zutrifft) und notiere seine Position.
Adapter
@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
Log.d(TAG, "onBindViewHolder() position: " + position);
...
}
Aus meiner Log
sehe ich, dass die Positionen 0-7 gebunden sind:
Ich habe eine selectAll()
Methode, die jede ViewHolder
durch die Adapterposition erhält. Wenn die zurückgegebene holder
NICHT null
ist, verwende ich die zurückgegebene holder
, um die Ansicht zu aktualisieren und die ausgewählte Ansicht anzuzeigen. Wenn der zurückgegebene Inhaber IS null
aufrufe selectOnBind()
, eine Methode, die die Ansicht an dieser Position markiert, wird sie so angezeigt, dass sie ausgewählt ist, wenn sie gebunden ist, und nicht in Echtzeit, da sie derzeit nicht angezeigt wird:
public void selectAll() {
for (int i = 0; i < numberOfItemsInList; i++) {
MyAdapter.ViewHolder holder = (MyAdapter.ViewHolder)
mRecyclerView.findViewHolderForAdapterPosition(i);
Log.d(TAG, "holder at position " + i + " is " + holder);
if (holder != null) {
select(holder);
} else {
selectOnBind(i);
}
}
}
In dieser Methode Log
die holder
zusammen mit ihrer Position:
Bis zu diesem Punkt scheint alles normal zu sein. Wir haben Positionen 0-7, die zeigen, und gemäß der Log
sind dies die Positionen, die gebunden sind. Wenn ich auf selectAll()
tippe, ohne die sichtbaren Ansichten zu ändern (Scrollen), sehe ich, dass die Positionen 0-7 definiert sind und 8-12 die Variable null
sind. So weit, ist es gut.
Hier wird es interessant. Wenn nach dem Aufruf von selectAll()
die Listenpositionen 8 und 9 weiter nach unten gescrollt werden, sind sie nicht ausgewählt.
Bei der Überprüfung von Log
stelle ich fest, dass sie niemals gebunden sind, obwohl berichtet wurde, dass sie null
sind:
Noch verwirrender ist, dass dies nicht jedes Mal geschieht. Wenn ich die App zum ersten Mal starte und diese teste, funktioniert sie möglicherweise. Aber es scheint danach unbedingt zu passieren. Ich vermute, es hat etwas mit den Ansichten zu tun, die recycelt werden, aber müssten sie auch nicht gebunden sein?
EDIT (6-29-16)
Nach einem AndroidStudio-Update kann ich den Fehler nicht reproduzieren. Es funktioniert wie erwartet und bindet die Null-Ansichten. Wenn dieses Problem erneut auftritt, werde ich zu diesem Beitrag zurückkehren.
Dies geschieht, weil:
getChildAt
funktioniert nicht und gibt für diese Position null zurück) onBind
wird nicht aufgerufen) Wenn Sie recyclerView.setItemViewCacheSize(0)
aufrufen, wird dieses "Problem" behoben.
Da der Standardwert 2 ist (private static final int DEFAULT_CACHE_SIZE = 2;
in RecyclerView.Recycler
), erhalten Sie immer 2 Ansichten, in denen nicht onBind
aufgerufen wird, die jedoch dem Recycler nicht hinzugefügt werden
In Ihrem Fall werden die Ansichten für die Positionen 8 und 9 nicht wiederverwendet, sie werden vom Fenster getrennt und wieder angebracht. Und für diese freistehende Ansicht wird onBindViewHolder
nicht aufgerufen, sondern nur onViewAttachedToWindow
. Wenn Sie diese Funktion in Ihrem Adapter überschreiben, können Sie sehen, worüber ich spreche.
@Override
public void onViewRecycled(ViewHolder vh){
Log.wtf(TAG,"onViewRecycled "+vh);
}
@Override
public void onViewDetachedFromWindow(ViewHolder viewHolder){
Log.wtf(TAG,"onViewDetachedFromWindow "+viewHolder);
}
Nun, um Ihr Problem zu lösen, müssen Sie die Ansichten verfolgen, die wiederverwendet werden sollten, aber getrennt werden und dann den Abschnittsprozess durchführen
@Override
public void onViewAttachedToWindow(ViewHolder viewHolder){
Log.wtf(TAG,"onViewAttachedToWindow "+viewHolder);
}
Die Antworten von Pedro Oliveira und Zartha sind großartig, um das Problem zu verstehen, aber ich sehe keine Lösungen, mit denen ich zufrieden bin.
Ich glaube, Sie haben zwei gute Optionen, je nachdem, was Sie tun:
Option 1
Wenn Sie möchten, dass onBindViewHolder()
für eine Ansicht außerhalb des Bildschirms angerufen wird, unabhängig davon, ob sie zwischengespeichert oder getrennt ist oder nicht, können Sie Folgendes tun:
RecyclerView.ViewHolder view_holder = recycler_view.findViewHolderForAdapterPosition( some_position );
if ( view_holder != null )
{
//manipulate the attached view
}
else //view is either non-existant or detached waiting to be reattached
notifyItemChanged( some_position );
Die Idee ist, dass, wenn die Ansicht zwischengespeichert/getrennt wird, der Adapter von notifyItemChanged()
informiert wird, dass die Ansicht ungültig ist, was dazu führt, dass onBindViewHolder()
aufgerufen wird.
Option 2
Wenn Sie nur eine Teiländerung durchführen möchten (und nicht alles in onBindViewHolder()
), müssen Sie in onBindViewHolder( ViewHolder view_holder, int position )
die position
im view_holder
speichern und die gewünschte Änderung in onViewAttachedToWindow( ViewHolder view_holder )
ausführen.
Ich empfehle Option 1 aus Gründen der Vereinfachung, es sei denn, Ihre onBindViewHolder()
führt etwas intensiveres durch, z. B. das Durcheinander mit Bitmaps.
Ich denke, Ihre Frage wird unten von @Pedro Oliveira beantwortet. Das Hauptgefühl von RecycleView besteht darin, dass er jederzeit spezielle Algorithmen zum Zwischenspeichern von ViewHolder verwendet. Als nächstes funktioniert onBindViewHolder (...) möglicherweise nicht. wenn die Ansicht statisch ist oder etwas anderes.
Und zu Ihrer Frage sollten Sie RecycleView für dynamisch geänderte Ansichten verwenden. TUN SIE NICHT! Da RecycleView die Ansichten ungültig macht und über ein Caching-System verfügt, werden Sie viele Probleme haben.
Use LinkedListView für diese Aufgabe!
Ich denke, mit Sicht zu spielen ist im Recycling-Bereich keine gute Idee. Der Ansatz, den ich immer verfolge, um einfach mit RecyclerView ein Flag in das Modell einzuführen. Nehmen wir an, Ihr Modell ist wie -
class MyModel{
String name;
int age;
}
Wenn Sie die Ansicht verfolgen, ist die Ansicht ausgewählt oder nicht, und fügen Sie dem Modell einen Booleschen Wert hinzu. Jetzt wird es aussehen -
class MyModel{
String name;
int age;
boolean isSelected;
}
Jetzt wird Ihr Kontrollkästchen auf der Grundlage des neuen Flags isSelected (in onBindViewHolder ()) aktiviert/deaktiviert. Bei jeder Auswahl in der Ansicht wird der Wert des entsprechenden Modellwerts in true und in nicht ausgewähltem Wert in false geändert. In Ihrem Fall führen Sie einfach eine Schleife aus, um den isSelected-Wert aller Modelle in true zu ändern, und rufen Sie dann notifyDataSetChanged()
auf.
Nehmen wir beispielsweise an, Ihre Liste ist
ArrayList<MyModel> recyclerList;
private void selectAll(){
for(MyModel myModel:recyclerList)
myModel.isSelected = true;
notifyDataSetChanged();
}
Mein Vorschlag, bei der Verwendung von RecyclerView oder ListView weniger zu versuchen, mit Ansichten zu spielen.
Also in deinem Fall -
@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
holder.clickableView.setTag(position);
holder.selectableView.setTag(position);
holder.checkedView.setChecked(recyclerList.get(position).isSelected);
Log.d(TAG, "onBindViewHolder() position: " + position);
...
}
@Override
public void onClick(View view){
int position = (int)view.getTag();
recyclerList.get(position).isSelected = !recyclerList.get(position).isSelected;
}
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
int position = (int)buttonView.getTag();
recyclerList.get(position).isSelected = isChecked;
}
Hoffe, es wird dir helfen, bitte lass es mich wissen, wenn du weitere Erklärungen brauchst :)
Wenn Sie eine große Anzahl von Elementen in der Liste haben, die Sie an recyclerview adapter
übergeben haben, tritt das Problem nicht auf, dass onBindViewHolder()
beim Scrollen nicht ausgeführt wird.
Wenn die Liste jedoch weniger Elemente enthält (ich habe die Listengröße 5 geprüft), kann dieses Problem auftreten.
Eine bessere Lösung ist, list size
zu überprüfen.
Beispielcode finden Sie unten.
private void setupAdapter(){
if (list.size() <= 10){
recycler.setItemViewCacheSize(0);
}
recycler.setAdapter(adapter);
recycler.setLayoutManager(linearLayoutManager);
}