RecyclerView blinking after notifyDatasetChanged()

AndroidAndroid FragmentsAndroid Recyclerview

Android Problem Overview


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?

Android Solutions


Solution 1 - Android

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.

Solution 2 - Android

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

Solution 3 - Android

This simply worked:

recyclerView.getItemAnimator().setChangeDuration(0);

Solution 4 - Android

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.

Solution 5 - Android

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)

Solution 6 - Android

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

Solution 7 - Android

In Kotlin you can use 'class extension' for 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()
    // ...
}

Solution 8 - Android

Kotlin solution:

(recyclerViewIdFromXML.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false

Solution 9 - Android

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.

Solution 10 - Android

In my case, neither any of above nor the answers from other stackoverflow questions having same problems worked.

Well, I was using custom animation each time the item gets clicked, for which I was calling notifyItemChanged(int position, Object Payload) to pass payload to my CustomAnimator class.

Notice, there are 2 onBindViewHolder(...) methods available in RecyclerView Adapter. onBindViewHolder(...) method having 3 parameters will always be called before onBindViewHolder(...) method having 2 parameters.

Generally, we always override the onBindViewHolder(...) method having 2 parameters and the main root of problem was I was doing the same, as each time notifyItemChanged(...) gets called, our onBindViewHolder(...) method will be called, in which I was loading my image in ImageView using Picasso, and this was the reason it was loading again regardless of its from memory or from internet. Until loaded, it was showing me the placeholder image, which was the reason of blinking for 1 sec whenever I click on the itemview.

Later, I also override another onBindViewHolder(...) method having 3 parameters. Here I check if the list of payloads is empty, then I return the super class implementation of this method, else if there are payloads, I am just setting the alpha value of the itemView of holder to 1.

And yay I got the solution to my problem after wasting a one full day sadly!

Here's my code for onBindViewHolder(...) methods:

onBindViewHolder(...) with 2 params:

@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(...) with 3 params:

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

Here's the code of method I was calling in onClickListener of viewHolder's itemView in onCreateViewHolder(...):

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

Note: You can get this position by calling getAdapterPosition() method of your viewHolder from onCreateViewHolder(...).

I have also overridden getItemId(int position) method as follows:

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

and called setHasStableIds(true); on my adapter object in activity.

Hope this helps if none of the answers above work!

Solution 11 - Android

In my case there was a much simpler problem, but it can look/feel very much like the problem above. I had converted an ExpandableListView to a RecylerView with Groupie (using Groupie's ExpandableGroup feature). My initial layout had a section like this:

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

With layout_height set to "wrap_content" the animation from expanded group to collapsed group felt like it would flash, but it was really just animating from the "wrong" position (even after trying most of the recommendations in this thread).

Anyway, simply changing layout_height to match_parent like this fixed the problem.

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

Solution 12 - Android

Try this in Kotlin

binding.recyclerView.apply {
    (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
}

Solution 13 - Android

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.

Solution 14 - Android

Try to use the stableId in recycler view. The following article briefly explains it

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

Solution 15 - Android

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

Solution 16 - Android

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

Solution 17 - Android

for me recyclerView.setHasFixedSize(true); worked

Solution 18 - Android

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!!

Solution 19 - Android

In my case I used SwipeRefresh and RecycleView with viewmodel binding and faced with blinking. Solved with -> Use submitList() to keep the list updated because DiffUtils have done the job, otherwise the list reloading entirely refer to CodeLab https://developer.android.com/codelabs/kotlin-android-training-diffutil-databinding#4

Solution 20 - Android

when using livedata I solved it with diffUtil This is how I combined diffUtill and databinding with my adapter

class ItemAdapter(
    private val clickListener: ItemListener
) :
    ListAdapter<Item, ItemAdapter.ViewHolder>(ItemDiffCallback()) {

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(clickListener, getItem(position)!!)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ViewHolder.from(parent)
    }

    class ViewHolder private constructor(val binding: ListItemViewBinding) :
        RecyclerView.ViewHolder(binding.root) {
        fun bind(
            clickListener: ItemListener,
            item: Item) {

            binding.item = item
            binding.clickListener = clickListener
            binding.executePendingBindings()
        }

        companion object {
            fun from(parent: ViewGroup): ViewHolder {
                val layoutInflater = LayoutInflater.from(parent.context)
                val view = ListItemViewBinding
                    .inflate(layoutInflater, parent, false)

                return ViewHolder(view)
            }
        }
    }

    class ItemDiffCallback :
        DiffUtil.ItemCallback<Item>() {
        override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
            return oldItem.itemId == newItem.itemId
        }

        override fun getChangePayload(oldItem: Item, newItem: Item): Any? {
            return newItem
        }

        override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
            return oldItem == newItem
        }
    }
}

class ItemListener(val clickListener: (item: Item) -> Unit) {
    fun onClick(item: Item) = clickListener(item)
}

Solution 21 - Android

In my case (shared element transition between one image to a higher resolution image), I added some delay to the item decorator in order to make it less noticeable:

(yourRv.itemAnimator as? DefaultItemAnimator)?.changeDuration = 2000

Solution 22 - Android

My own issue was a very specific problem which I'm going to share some insight for here, though I don't yet fully understand it.

Basically I had a re-usable fragment with RecyclerView, so that I could have a menu with nested menus. Pick an item in the RecyclerView, and it opens another fragment with more options. All facilitated using the JetPack navigation component and data binding using LiveData in the layout xml.

Anyway, here's how I fixed my issue of items flickering when the RecyclerView changed (although it's worth bearing in mind, this was only the appearance as it was a 'new' RecyclerView each time). To update the LiveData list of items (some viewmodels representing objects for the menu items, in my case) I was using LiveData.value = new items. Changing it to postValue(new items) fixed the issue, though I'm not yet sure why.

I've read up the difference between value (setValue in Java) and PostValue, and I understand they're to do with using the main thread or background threading, and the latter only gets applied one time on the main thread when it's ready. But other than that, I'm not sure why this fixed flickering in my RecyclerView. Maybe someone has some insight? In any case, hopefully this will help someone facing a similar problem to me.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAliView Question on Stackoverflow
Solution 1 - AndroidAnatoly VdovichevView Answer on Stackoverflow
Solution 2 - AndroidSabeerView Answer on Stackoverflow
Solution 3 - AndroidHamzeh SobohView Answer on Stackoverflow
Solution 4 - AndroidWeselyView Answer on Stackoverflow
Solution 5 - AndroidMohamed FaroukView Answer on Stackoverflow
Solution 6 - AndroidvvbYWf0ugJOGNA3ACVxpView Answer on Stackoverflow
Solution 7 - AndroidGregoryView Answer on Stackoverflow
Solution 8 - AndroidRobert PalView Answer on Stackoverflow
Solution 9 - AndroidyigitView Answer on Stackoverflow
Solution 10 - AndroidParth BhanushaliView Answer on Stackoverflow
Solution 11 - AndroidTomView Answer on Stackoverflow
Solution 12 - AndroidThiagoView Answer on Stackoverflow
Solution 13 - AndroidAndroid learnerView Answer on Stackoverflow
Solution 14 - AndroidFarruh HabibullaevView Answer on Stackoverflow
Solution 15 - Androiddeveloper_androidView Answer on Stackoverflow
Solution 16 - AndroidDeeferView Answer on Stackoverflow
Solution 17 - AndroidPramodView Answer on Stackoverflow
Solution 18 - AndroidSreedhu MadhuView Answer on Stackoverflow
Solution 19 - AndroidBurchikView Answer on Stackoverflow
Solution 20 - Androidmahmood View Answer on Stackoverflow
Solution 21 - AndroidPhilView Answer on Stackoverflow
Solution 22 - AndroidChuckyView Answer on Stackoverflow