RecyclerView blinking after notifyDatasetChanged()

2019-01-21 12:05发布

问题:

I have a RecyclerView which loads some data from API, includes an image url and some data, and I use networkImageView to lazy load image.

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

Here is implementation for 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());
    }

Problem is when we have refresh in the recyclerView, it is blincking for a very short while in the beginning which looks strange.

I just used GridView/ListView instead and it worked as I expected. There were no blincking.

configuration for 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);

Anyone faced with such a problem? what could be the reason?

回答1:

Try using stable IDs in your RecyclerView.Adapter

setHasStableIds(true) and override getItemId(int position).

Without stable IDs, after notifyDataSetChanged(), ViewHolders usually assigned to not to same positions. That was the reason of blinking in my case.

You can find a good explanation here.



回答2:

According to this issue page ....it is the default recycleview item change animation... You can turn it off.. try this

recyclerView.getItemAnimator().setSupportsChangeAnimations(false);

Change in latest version

Quoted from Android developer blog:

Note that this new API is not backward compatible. If you previously implemented an ItemAnimator, you can instead extend SimpleItemAnimator, which provides the old API by wrapping the new API. You’ll also notice that some methods have been entirely removed from ItemAnimator. For example, if you were calling recyclerView.getItemAnimator().setSupportsChangeAnimations(false), this code won’t compile anymore. You can replace it with:

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


回答3:

This simply worked:

recyclerView.getItemAnimator().setChangeDuration(0);


回答4:

I have the same issue loading image from some urls and then imageView blinks. Solved by using

notifyItemRangeInserted()    

instead of

notifyDataSetChanged()

which avoids to reload those unchanged old datas.



回答5:

try this to disable the default animation

ItemAnimator animator = recyclerView.getItemAnimator();

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

this the new way to disable the animation since android support 23

this old way will work for older version of the support library

recyclerView.getItemAnimator().setSupportsChangeAnimations(false)


回答6:

Assuming mItems is the collection that backs your Adapter, why are you removing everything and re-adding? You are basically telling it that everything has changed, so RecyclerView rebinds all views than I assume the Image library does not handle it properly where it still resets the View even though it is the same image url. Maybe they had some baked in solution for AdapterView so that it works fine in GridView.

Instead of calling notifyDataSetChanged which will cause re-binding all views, call granular notify events (notify added/removed/moved/updated) so that RecyclerView will rebind only necessary views and nothing will flicker.



回答7:

Hey @Ali it might be late replay. I also faced this issue and solved with below solution, it may help you please check.

LruBitmapCache.java class is created to get image cache size

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 singleton class [extends Application] added below code

in VolleyClient singleton class constructor add below snippet to initialize the ImageLoader

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

I created getLruBitmapCache() method to return LruBitmapCache

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

Hope its going to help you.



回答8:

Recyclerview uses DefaultItemAnimator as it's default animator. As you can see from the code below, they change the alpha of the view holder upon item change:

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

I wanted to retain the rest of the animations but remove the "flicker" so I cloned DefaultItemAnimator and removed the 3 alpha lines above.

To use the new animator just call setItemAnimator() on your RecyclerView:

mRecyclerView.setItemAnimator(new MyItemAnimator());


回答9:

I had similar issue and this worked for me You can call this method to set size for image cache

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


回答10:

for my application, I had some data changing but I didn't want the entire view to blink.

I solved it by only fading the oldview down 0.5 alpha and starting the newview alpha at 0.5. This created a softer fading transition without making the view disappear completely.

Unfortunately because of private implementations, I couldn't subclass the DefaultItemAnimator in order to make this change so I had to clone the code and make the following changes

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


回答11:

for me recyclerView.setHasFixedSize(true); worked



回答12:

Using appropriate recyclerview methods to update views will solve this issue

First, make changes in the list

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

Then notify using

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

Hope this will help!!