web-dev-qa-db-fra.com

RecyclerView clignote après notifyDatasetChanged ()

J'ai un RecyclerView qui charge certaines données de l'API, comprend une URL d'image et certaines données, et j'utilise networkImageView pour charger l'image paresseux.

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

Voici l'implémentation pour 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());
    }

Le problème, c’est que lorsque nous avons actualisé le recyclerView, il clignote pendant un très court instant au début, ce qui semble étrange.

Je viens d'utiliser GridView/ListView à la place et cela a fonctionné comme je m'y attendais. Il n'y avait pas de blincking.

configuration pour RecycleView dans 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);

Quelqu'un est confronté à un tel problème? Quelle pourrait être la raison?

98
Ali

Essayez d’utiliser des identifiants stables dans votre RecyclerView.Adapter

setHasStableIds(true) et remplace getItemId(int position).

Sans ID stable, après notifyDataSetChanged(), les ViewHolders ne sont généralement pas affectés aux mêmes positions. C'était la raison de cligner des yeux dans mon cas.

Vous pouvez trouver un bonne explication ici.

105
Anatoly Vdovichev

Selon cette numéro page .... c'est l'animation de changement d'élément de recycleview par défaut ... Vous pouvez le désactiver .. essayez ceci

recyclerView.getItemAnimator().setSupportsChangeAnimations(false);

Changement dans la dernière version

Cité de blog de développeur Android :

Notez que cette nouvelle API n'est pas compatible avec les versions antérieures. Si vous avez précédemment implémenté un ItemAnimator, vous pouvez plutôt étendre SimpleItemAnimator, qui fournit l'ancienne API en encapsulant la nouvelle API. Vous remarquerez également que certaines méthodes ont été entièrement supprimées de ItemAnimator. Par exemple, si vous appeliez recyclerView.getItemAnimator (). SetSupportsChangeAnimations (false), ce code ne sera plus compilé. Vous pouvez le remplacer par:

ItemAnimator animator = recyclerView.getItemAnimator();
if (animator instanceof SimpleItemAnimator) {
  ((SimpleItemAnimator) animator).setSupportsChangeAnimations(false);
}
93
Sabeer Mohammed

Cela a simplement fonctionné:

recyclerView.getItemAnimator().setChangeDuration(0);
37
Hamzeh Soboh

J'ai le même problème de chargement d'image à partir de certaines URL, puis imageView clignote. Résolu en utilisant

notifyItemRangeInserted()    

au lieu de

notifyDataSetChanged()

ce qui évite de recharger ces anciennes données inchangées.

9
Wesely

essayez ceci pour désactiver l'animation par défaut

ItemAnimator animator = recyclerView.getItemAnimator();

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

c'est la nouvelle façon de désactiver l'animation depuis Android support 23

cette ancienne méthode fonctionnera pour l'ancienne version de la bibliothèque de support

recyclerView.getItemAnimator().setSupportsChangeAnimations(false)
9
Mohamed Farouk

En supposant que mItems soit la collection qui sauvegarde votre Adapter, pourquoi supprimez-vous tout et rajoutez-vous? En gros, vous lui dites que tout a changé. Ainsi, RecyclerView lie à nouveau toutes les vues, car je suppose que la bibliothèque d’images ne la gère pas correctement, même si elle réinitialise la vue, même s’il s’agit de la même adresse URL. Peut-être qu'ils avaient une solution intégrée pour AdapterView afin que cela fonctionne correctement dans GridView.

Au lieu d'appeler notifyDataSetChanged, ce qui entraînera la liaison de toutes les vues, appelez des événements de notification granulaires (notification ajoutée/supprimée/déplacée/mise à jour) afin que RecyclerView ne lie à nouveau que les vues nécessaires et que rien ne scintille.

2
yigit

Recyclerview utilise DefaultItemAnimator comme animateur par défaut. Comme vous pouvez le voir dans le code ci-dessous, ils modifient l'alpha du détenteur de la vue lors du changement d'élément:

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

Je voulais conserver le reste des animations, mais supprimer le "scintillement". J'ai donc cloné DefaultItemAnimator et supprimé les 3 lignes alpha ci-dessus.

Pour utiliser le nouvel animateur, appelez simplement setItemAnimator () sur votre RecyclerView:

mRecyclerView.setItemAnimator(new MyItemAnimator());
2
Peter File

Essayez d'utiliser la propriété stableId dans la vue Recycler. L'article suivant l'explique brièvement

https://medium.com/@hanru.yeh/recyclerviews-views-are-blinking-when-notifydatasetchanged-c7b76d5149a2

1
Farruh Habibullaev

Hey @ ALi, il pourrait être tard en replay. J'ai également fait face à ce problème et résolu avec la solution ci-dessous, il peut vous aider s'il vous plaît vérifier.

LruBitmapCache.Java la classe est créée pour obtenir la taille du cache d'images

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

VolleyClient.Java classe singleton [extension de l'application] ajoutée sous le code

dans le constructeur de classe singleton VolleyClient, ajoutez le fragment ci-dessous pour initialiser ImageLoader.

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

J'ai créé la méthode getLruBitmapCache () pour renvoyer LruBitmapCache

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

J'espère que ça va vous aider.

1
Android learner

Dans Kotlin, vous pouvez utiliser "extension de classe" pour RecyclerView:

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()
    // ...
}
1
Gregory

L'utilisation de méthodes de recyclage appropriées pour mettre à jour les vues résoudra ce problème

Tout d'abord, apportez des modifications à la liste

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

Puis notifiez en utilisant

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

J'espère que ça va aider !!

0
Sreedhu Madhu

pour moi recyclerView.setHasFixedSize(true); travaillé

0
Pramod

pour mon application, certaines données ont été modifiées, mais je ne voulais pas que la vue entière clignote.

Je l'ai résolu en atténuant uniquement oldview avec une valeur inférieure à 0,5 alpha et en démarrant l'option newview alpha à 0,5. Cela a créé une transition plus atténuée sans faire disparaître complètement la vue.

Malheureusement, à cause d'implémentations privées, je ne pouvais pas sous-classer le DefaultItemAnimator afin d'effectuer cette modification. J'ai donc dû cloner le code et apporter les modifications suivantes.

dans animateChange:

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

dans animateChangeImpl:

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

J'ai eu un problème similaire et cela a fonctionné pour moi Vous pouvez appeler cette méthode pour définir la taille du cache d'images

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