RecyclerView blinkt nach notifyDatasetChanged ()

112

Ich habe eine RecyclerView, die einige Daten von der API lädt, eine Bild-URL und einige Daten enthält, und ich verwende networkImageView, um das Bild verzögert zu laden.

@Override
public void onResponse(List<Item> response) {
   mItems.clear();
   for (Item item : response) {
      mItems.add(item);
   }
   mAdapter.notifyDataSetChanged();
   mSwipeRefreshLayout.setRefreshing(false);
}

Hier ist die Implementierung für Adapter:

public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, final int position) {
        if (isHeader(position)) {
            return;
        }
        // - get element from your dataset at this position
        // - replace the contents of the view with that element
        MyViewHolder holder = (MyViewHolder) viewHolder;
        final Item item = mItems.get(position - 1); // Subtract 1 for header
        holder.title.setText(item.getTitle());
        holder.image.setImageUrl(item.getImg_url(), VolleyClient.getInstance(mCtx).getImageLoader());
        holder.image.setErrorImageResId(android.R.drawable.ic_dialog_alert);
        holder.origin.setText(item.getOrigin());
    }

Das Problem ist, wenn wir in der recyclerView eine Aktualisierung haben, blinkt sie am Anfang für eine sehr kurze Zeit, was seltsam aussieht.

Ich habe stattdessen nur GridView / ListView verwendet und es hat wie erwartet funktioniert. Es gab kein Blinzeln.

Konfiguration für RecycleView in onViewCreated of my Fragment:

mRecyclerView = (RecyclerView) view.findViewById(R.id.recyclerView);
        // use this setting to improve performance if you know that changes
        // in content do not change the layout size of the RecyclerView
        mRecyclerView.setHasFixedSize(true);

        mGridLayoutManager = (GridLayoutManager) mRecyclerView.getLayoutManager();
        mGridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
            @Override
            public int getSpanSize(int position) {
                return mAdapter.isHeader(position) ? mGridLayoutManager.getSpanCount() : 1;
            }
        });

        mRecyclerView.setAdapter(mAdapter);

Hat jemand ein solches Problem? Was könnte der Grund sein?

Ali
quelle

Antworten:

126

Versuchen Sie, stabile IDs in Ihrem RecyclerView.Adapter zu verwenden

setHasStableIds(true)und überschreiben getItemId(int position).

Ohne stabile IDs werden notifyDataSetChanged()ViewHolders danach normalerweise nicht denselben Positionen zugewiesen. Das war in meinem Fall der Grund zu blinken.

Eine gute Erklärung finden Sie hier.

Anatoly Vdovichev
quelle
1
Ich habe setHasStableIds (true) hinzugefügt, wodurch das Flackern behoben wurde, aber in meinem GridLayout ändern Elemente beim Scrollen immer noch ihre Position. Was soll ich in getItemId () überschreiben? Vielen Dank
Balázs Orbán
3
Irgendeine Idee, wie wir die ID generieren sollen?
Mauker
Du bist toll! Google ist NICHT. Google weiß entweder nichts davon oder sie wissen es und wollen nicht, dass wir es wissen! DANKE MANN
MBH
1
Verwirren Sie die Positionen der Gegenstände, die Gegenstände stammen aus der Firebase-Datenbank in der richtigen Reihenfolge.
MuhammadAliJr
1
@Mauker Wenn Ihr Objekt eine eindeutige Nummer hat, können Sie diese verwenden, oder wenn Sie eine eindeutige Zeichenfolge haben, können Sie object.hashCode () verwenden. Das funktioniert ganz gut für mich
Simon Schubert
106

Laut dieser Problemseite ... ist dies die Standardanimation zum Ändern von Recyclingview-Elementen ... Sie können sie deaktivieren ... versuchen Sie dies

recyclerView.getItemAnimator().setSupportsChangeAnimations(false);

Änderung in der neuesten Version

Zitiert aus dem Android-Entwicklerblog :

Beachten Sie, dass diese neue API nicht abwärtskompatibel ist. Wenn Sie zuvor einen ItemAnimator implementiert haben, können Sie stattdessen SimpleItemAnimator erweitern, der die alte API bereitstellt, indem Sie die neue API umschließen. Sie werden auch feststellen, dass einige Methoden vollständig aus ItemAnimator entfernt wurden. Wenn Sie beispielsweise recyclerView.getItemAnimator (). SetSupportsChangeAnimations (false) aufrufen, wird dieser Code nicht mehr kompiliert. Sie können es ersetzen durch:

ItemAnimator animator = recyclerView.getItemAnimator();
if (animator instanceof SimpleItemAnimator) {
  ((SimpleItemAnimator) animator).setSupportsChangeAnimations(false);
}
Sabeer Mohammed
quelle
5
@sabeer immer noch das gleiche Problem. Das Problem wird nicht behoben.
Shreyash Mahajan
3
@ Delive lassen Sie mich wissen, wenn Sie eine Lösung für dieses gefunden haben
Shreyash Mahajan
Ich benutze Picasso, es ist etwas, das nicht blinkt.
liefern
8
Kotlin: (recyclerView.itemAnimator als? SimpleItemAnimator)? UnterstütztChangeAnimations = false
Pedro Paulo Amorim
Es funktioniert, aber es kommt ein anderes Problem (NPE) um das zu lösen?
Navas pk
45

Das hat einfach funktioniert:

recyclerView.getItemAnimator().setChangeDuration(0);
Hamzeh Soboh
quelle
Es ist eine gute Alternative zu codeItemAnimator animator = recyclerView.getItemAnimator (); if (Animatorinstanz von SimpleItemAnimator) {((SimpleItemAnimator) Animator) .setSupportsChangeAnimations (false); }code
Ziniestro
1
stoppt alle Animationen ADD und REMOVE
MBH
11

Ich habe das gleiche Problem beim Laden des Bildes von einigen URLs und dann blinkt imageView. Gelöst mit

notifyItemRangeInserted()    

anstatt

notifyDataSetChanged()

Dadurch wird vermieden, dass diese unveränderten alten Daten neu geladen werden.

Wesely
quelle
9

Versuchen Sie dies, um die Standardanimation zu deaktivieren

ItemAnimator animator = recyclerView.getItemAnimator();

if (animator instanceof SimpleItemAnimator) {
  ((SimpleItemAnimator) animator).setSupportsChangeAnimations(false);
}

Dies ist die neue Möglichkeit, die Animation zu deaktivieren, da Android 23 unterstützt

Diese alte Methode funktioniert für ältere Versionen der Support-Bibliothek

recyclerView.getItemAnimator().setSupportsChangeAnimations(false)
Mohamed Farouk
quelle
4

Angenommen, mItemsdie Sammlung unterstützt Sie Adapter. Warum entfernen Sie alles und fügen es erneut hinzu? Sie sagen im Grunde, dass sich alles geändert hat, sodass RecyclerView alle Ansichten neu bindet, als ich annehme, dass die Bildbibliothek nicht richtig damit umgeht, wenn die Ansicht immer noch zurückgesetzt wird, obwohl es sich um dieselbe Bild-URL handelt. Vielleicht hatten sie eine Lösung für AdapterView eingebrannt, damit es in GridView gut funktioniert.

Anstatt aufzurufen, notifyDataSetChangedwodurch alle Ansichten erneut gebunden werden, rufen Sie granulare Benachrichtigungsereignisse auf (Benachrichtigung hinzugefügt / entfernt / verschoben / aktualisiert), damit RecyclerView nur die erforderlichen Ansichten neu bindet und nichts flackert.

Yigit
quelle
3
Oder ist es nur ein "Fehler" in RecyclerView? Wenn es in den letzten 6 Jahren mit AbsListView gut funktioniert hat und jetzt nicht mehr mit RecyclerView, bedeutet dies natürlich, dass mit RecyclerView etwas nicht in Ordnung ist, oder? :) Ein kurzer Blick darauf zeigt, dass beim Aktualisieren von Daten in ListView und GridView die Ansicht + Position verfolgt wird. Wenn Sie also aktualisieren, erhalten Sie genau den gleichen Ansichtsinhaber. Während RecyclerView die Ansichtshalter mischt, führt dies zu Flackern.
Vovkab
Die Arbeit an ListView bedeutet nicht, dass es für RecyclerView korrekt ist. Diese Komponenten haben unterschiedliche Architekturen.
Yigit
1
Stimmen Sie zu, aber manchmal ist es wirklich schwierig zu wissen, welche Elemente geändert werden, z. B. wenn Sie Cursor verwenden oder nur Ihre gesamten Daten aktualisieren. Daher sollte recyclerview auch diesen Fall korrekt behandeln.
Vovkab
4

Recyclerview verwendet DefaultItemAnimator als Standardanimator. Wie Sie dem folgenden Code entnehmen können, ändern sie das Alpha des Ansichtsinhabers beim Ändern des Elements:

@Override
public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromX, int fromY, int toX, int toY) {
    ...
    final float prevAlpha = ViewCompat.getAlpha(oldHolder.itemView);
    ...
    ViewCompat.setAlpha(oldHolder.itemView, prevAlpha);
    if (newHolder != null) {
        ....
        ViewCompat.setAlpha(newHolder.itemView, 0);
    }
    ...
    return true;
}

Ich wollte den Rest der Animationen beibehalten, aber das "Flimmern" entfernen, also habe ich DefaultItemAnimator geklont und die 3 Alpha-Linien oben entfernt.

Um den neuen Animator zu verwenden, rufen Sie einfach setItemAnimator () in Ihrer RecyclerView auf:

mRecyclerView.setItemAnimator(new MyItemAnimator());
Peter File
quelle
Es beseitigte das Blinken, verursachte jedoch aus irgendeinem Grund einen Flackereffekt.
Mohamed Medhat
4

In Kotlin können Sie die Klassenerweiterung für RecyclerView verwenden:

fun RecyclerView.disableItemAnimator() {
    (itemAnimator as? SimpleItemAnimator)?.supportsChangeAnimations = false
}

// sample of using in Activity:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                          savedInstanceState: Bundle?): View? {
    // ...
    myRecyclerView.disableItemAnimator()
    // ...
}
Gregory
quelle
1

Hey @Ali, es könnte eine späte Wiederholung sein. Ich habe mich auch diesem Problem gestellt und es mit der folgenden Lösung gelöst. Es kann Ihnen helfen, dies zu überprüfen.

Die LruBitmapCache.java- Klasse wird erstellt, um die Größe des Bildcaches zu ermitteln

import android.graphics.Bitmap;
import android.support.v4.util.LruCache;
import com.android.volley.toolbox.ImageLoader.ImageCache;

public class LruBitmapCache extends LruCache<String, Bitmap> implements
        ImageCache {
    public static int getDefaultLruCacheSize() {
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        final int cacheSize = maxMemory / 8;

        return cacheSize;
    }

    public LruBitmapCache() {
        this(getDefaultLruCacheSize());
    }

    public LruBitmapCache(int sizeInKiloBytes) {
        super(sizeInKiloBytes);
    }

    @Override
    protected int sizeOf(String key, Bitmap value) {
        return value.getRowBytes() * value.getHeight() / 1024;
    }

    @Override
    public Bitmap getBitmap(String url) {
        return get(url);
    }

    @Override
    public void putBitmap(String url, Bitmap bitmap) {
        put(url, bitmap);
    }
}

Die VolleyClient.java- Singleton-Klasse [erweitert die Anwendung] wurde unter dem Code hinzugefügt

Fügen Sie im VolleyClient-Singleton-Klassenkonstruktor das folgende Snippet hinzu, um den ImageLoader zu initialisieren

private VolleyClient(Context context)
    {
     mCtx = context;
     mRequestQueue = getRequestQueue();
     mImageLoader = new ImageLoader(mRequestQueue,getLruBitmapCache());
}

Ich habe die Methode getLruBitmapCache () erstellt, um LruBitmapCache zurückzugeben

public LruBitmapCache getLruBitmapCache() {
        if (mLruBitmapCache == null)
            mLruBitmapCache = new LruBitmapCache();
        return this.mLruBitmapCache;
}

Hoffe, es wird dir helfen.

Android-Lernender
quelle
Danke für deine Antwort. Genau das habe ich in meiner VollyClient.java getan. Werfen
Ali
Überprüfen Sie es einfach einmal mit der LruCache <String, Bitmap> -Klasse. Ich denke, es wird Ihr Problem lösen. Werfen
Android-Lernende
Haben Sie einmal den Code überprüft, den ich Ihnen im Kommentar mitgeteilt habe? Was hast du in deiner Klasse, das ich dort vermisst habe?
Ali
Es fehlt Ihnen die Erweiterung der LruCache <String, Bitmap> -Klasse und die überschreibende sizeOf () -Methode, wie ich es getan habe.
Android-Lerner
Ok, ich probiere es sehr bald aus, aber kannst du mir erklären, was du dort getan hast, was die Magie für dich getan und das Problem gelöst hat? Quellcode Für mich klingt es so, als ob Ihre überschriebene sizeOf-Methode im Quellcode enthalten sein sollte.
Ali
1

für mich hat recyclerView.setHasFixedSize(true);gearbeitet

Pramod
quelle
Ich glaube nicht, dass die Artikel von OP eine feste Größe haben
Irfandi D. Vendy
das hat mir in meinem
fall
1

In meinem Fall funktionierten weder die oben genannten noch die Antworten von anderen Stackoverflow-Fragen mit denselben Problemen.

Nun, ich habe jedes Mal, wenn auf das Element geklickt wird, eine benutzerdefinierte Animation verwendet, für die ich notifyItemChanged (int position, Object Payload) aufgerufen habe, um die Nutzdaten an meine CustomAnimator-Klasse zu übergeben.

Beachten Sie, dass im RecyclerView-Adapter zwei onBindViewHolder-Methoden (...) verfügbar sind. Die Methode onBindViewHolder (...) mit 3 Parametern wird immer vor der Methode onBindViewHolder (...) mit 2 Parametern aufgerufen.

Im Allgemeinen überschreiben wir immer die onBindViewHolder (...) -Methode mit 2 Parametern und die Hauptursache des Problems war, dass ich dasselbe tat, da jedes Mal, wenn notifyItemChanged (...) aufgerufen wird, unsere onBindViewHolder (...) -Methode ausgeführt wird aufgerufen werden, in dem ich mein Bild mit Picasso in ImageView geladen habe, und dies war der Grund, warum es erneut geladen wurde, unabhängig davon, ob es aus dem Speicher oder aus dem Internet stammt. Bis zum Laden wurde mir das Platzhalterbild angezeigt. Dies war der Grund, warum ich 1 Sekunde lang blinkte, wenn ich auf die Elementansicht klickte.

Später überschreibe ich auch eine andere onBindViewHolder (...) -Methode mit 3 Parametern. Hier überprüfe ich, ob die Liste der Nutzdaten leer ist, und gebe dann die Super-Klassen-Implementierung dieser Methode zurück. Wenn es Nutzdaten gibt, setze ich nur den Alpha-Wert der itemView des Inhabers auf 1.

Und ja, ich habe die Lösung für mein Problem gefunden, nachdem ich einen ganzen Tag leider verschwendet habe!

Hier ist mein Code für onBindViewHolder (...) Methoden:

onBindViewHolder (...) mit 2 Parametern:

@Override
public void onBindViewHolder(@NonNull RecyclerAdapter.ViewHolder viewHolder, int position) {
            Movie movie = movies.get(position);

            Picasso.with(context)
                    .load(movie.getImageLink())
                    .into(viewHolder.itemView.posterImageView);
    }

onBindViewHolder (...) mit 3 Parametern:

@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List<Object> payloads) {
        if (payloads.isEmpty()) {
            super.onBindViewHolder(holder, position, payloads);
        } else {
            holder.itemView.setAlpha(1);
        }
    }

Hier ist der Code der Methode, die ich in onClickListener von viewHolder's itemView in onCreateViewHolder (...) aufgerufen habe:

private void onMovieClick(int position, Movie movie) {
        Bundle data = new Bundle();
        data.putParcelable("movie", movie);

        // This data(bundle) will be passed as payload for ItemHolderInfo in our animator class
        notifyItemChanged(position, data);
    }

Hinweis: Sie können diese Position erhalten, indem Sie die Methode getAdapterPosition () Ihres viewHolder von onCreateViewHolder (...) aufrufen.

Ich habe auch die Methode getItemId (int position) wie folgt überschrieben:

@Override
public long getItemId(int position) {
    Movie movie = movies.get(position);
    return movie.getId();
}

und setHasStableIds(true);mein Adapterobjekt in Aktivität aufgerufen .

Hoffe das hilft wenn keine der oben genannten Antworten funktioniert!

Parth Bhanushali
quelle
1

In meinem Fall gab es ein viel einfacheres Problem, aber es kann dem obigen Problem sehr ähnlich sein / sich so anfühlen. Ich hatte eine ExpandableListView mit Groupie in eine RecylerView konvertiert (mithilfe der ExpandableGroup-Funktion von Groupie). Mein ursprüngliches Layout hatte einen Abschnitt wie diesen:

<androidx.recyclerview.widget.RecyclerView
  android:id="@+id/hint_list"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:background="@android:color/white" />

Wenn layout_height auf "wrap_content" gesetzt war, fühlte sich die Animation von der erweiterten Gruppe zur reduzierten Gruppe so an, als würde sie blinken, aber es wurde wirklich nur von der "falschen" Position aus animiert (selbst nachdem die meisten Empfehlungen in diesem Thread ausprobiert wurden).

Das einfache Ändern von layout_height in match_parent auf diese Weise hat das Problem behoben.

<androidx.recyclerview.widget.RecyclerView
  android:id="@+id/hint_list"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="@android:color/white" />
Tom
quelle
0

Ich hatte ein ähnliches Problem und dies funktionierte bei mir. Sie können diese Methode aufrufen, um die Größe für den Bildcache festzulegen

private int getCacheSize(Context context) {

    final DisplayMetrics displayMetrics = context.getResources().
            getDisplayMetrics();
    final int screenWidth = displayMetrics.widthPixels;
    final int screenHeight = displayMetrics.heightPixels;
    // 4 bytes per pixel
    final int screenBytes = screenWidth * screenHeight * 4;

    return screenBytes * 3;
}
developer_android
quelle
0

Für meine Anwendung wurden einige Daten geändert, aber ich wollte nicht, dass die gesamte Ansicht blinkt.

Ich habe es gelöst, indem ich nur die alte Ansicht um 0,5 Alpha verkleinert und die neue Ansicht Alpha um 0,5 gestartet habe. Dies erzeugte einen weicheren Überblendungsübergang, ohne dass die Ansicht vollständig verschwand.

Leider konnte ich den DefaultItemAnimator aufgrund privater Implementierungen nicht in Unterklassen unterteilen, um diese Änderung vorzunehmen. Daher musste ich den Code klonen und die folgenden Änderungen vornehmen

in animateChange:

ViewCompat.setAlpha(newHolder.itemView, 0);  //change 0 to 0.5f

in animateChangeImpl:

oldViewAnim.alpha(0).setListener(new VpaListenerAdapter() { //change 0 to 0.5f
Deefer
quelle
0

Durch die Verwendung geeigneter Recyclerview-Methoden zum Aktualisieren von Ansichten wird dieses Problem behoben

Nehmen Sie zunächst Änderungen in der Liste vor

mList.add(item);
or mList.addAll(itemList);
or mList.remove(index);

Dann benachrichtigen Sie mit

notifyItemInserted(addedItemIndex);
or
notifyItemRemoved(removedItemIndex);
or
notifyItemRangeChanged(fromIndex, newUpdatedItemCount);

Hoffe das wird helfen !!

Sreedhu Madhu
quelle
Absolut nicht. Wenn Sie mehrere Aktualisierungen pro Sekunde vornehmen (in meinem Fall BLE-Scannen), funktioniert dies überhaupt nicht. Ich habe einen Tag damit verbracht, auf diese Scheiße zu aktualisieren. RecyclerAdapter ... Besser ArrayAdapter behalten. Es ist eine Schande, dass kein MVC-Muster verwendet wird, aber zumindest ist es fast brauchbar.
Gojir4