How to prevent RecyclerView item from blinking after notifyItemChanged(pos)?

AndroidAndroid Recyclerview

Android Problem Overview


I currently have a recycler view whose data updates every 5 secs. To update the data on the list, I am using

notifyItemChanged(position);
notifyItemRangeChanged(position, mList.size());

Each time I call notifyItemChanged(), the items on my recycler view update properly, however, it will blink because this causes onBindViewHolder to be called again. So it's as though it is a fresh load each time. How can I prevent this from happening, if possible?

Android Solutions


Solution 1 - Android

RecyclerView has built in animations which usually add a nice polished effect. in your case you'll want to disable them:

((SimpleItemAnimator) mRecyclerView.getItemAnimator()).setSupportsChangeAnimations(false);

(The default recycler view animator should already be an instance of SimpleItemAnimator)

For Kotlin,

(mRecyclerView?.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false

Solution 2 - Android

You can disable the item animations.

mRecyclerView.setItemAnimator(null);

Solution 3 - Android

The problem may not come from animation but from not stable id of the list item.

> To use stable IDs, you need to:

  • setHasStableIds(true)
    In RecyclerView.Adapter, we need to set setHasStableIds(true); true means this adapter would publish a unique value as a key for item in data set. Adapter can use the key to indicate they are the same one or not after notifying data changed.

  • override getItemId(int position)
    Then we must override getItemId(int position), to return identified long for the item at position. We need to make sure there is no different item data with the same returned id.

The source of solution for that is here.

Solution 4 - Android

Use stableId in your adapter.

Call adapter.setHasStableIds(true) and override getItemId(int position) method in your adapter class.

Also, return some unique id from getItemId(int position) for each item. Don't just simply return position.

Solution 5 - Android

If you want to keep RecyclerView's animation and avoid item flash in the same time, you can follow the steps below:

1. change view component status directly 2. change data in Adapter directly

  1. don't need to refresh UI manually by calling notifyItemChanged()

Just change the view directly would not make change immediately, the better way is using payload to do it.

In your adapter:

override fun onBindViewHolder(
        holder: RecyclerView.ViewHolder,
        position: Int,
        payloads: MutableList<Any>
    ) {
        // Using payload to refresh partly
        if (payloads.isNullOrEmpty()) {
            // refresh all 
            onBindViewHolder(holder, position)
            return
        }
        // just change one component's status which would not "flash".
        (holder as YourHolder).apply{
            // make you view change
            // etc. checkBox.isChecked = true
        }
    }

And using it:


yourAdapter.notifyItemChanged(position, "sample_pay_load")

Here is notifyitemchanged's Doc

Solution 6 - Android

This happening because Adapter is creating new ViewHolder and trying to animate between old and new ViewHolders. That's why all the "disable animations" answers technically work.

Instead you can just tell the RecyclerView to re-use Viewholders with canReuseUpdatedViewHolder(). See this answer to a similar question: https://stackoverflow.com/a/60427676/1650674

Solution 7 - Android

I read the below solution and implemented and it works fine on every device I tested.

  • Clone Default animator class.

  • In animateChange() method, comment below 3 lines,

     final float prevAlpha = oldHolder.itemView.getAlpha();
     .
     .
     oldHolder.itemView.setAlpha(prevAlpha);
     .
     .
     newHolder.itemView.setAlpha(0);
    
  • Set recyclerview item animator to that of your cloned class.

//Note: I do understand how the solution works with not changing the alpha value of new holder but this is not my solution, I read this on stackoverflow itself but for some reason could not find it anymore. Sharing this to help out fellow developers.

Solution 8 - 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 9 - Android

Additionaly, try to run each notifyItemChanged in a separate launched coroutine job inside Main scope. It worked perfectly for me removing blinking significantly.

for (i in 0 until adapter.itemCount) {
    CoroutineScope(Main).launch {
        adapter.notifyItemChanged(i)
    }
}

Solution 10 - Android

If only the content inside the view needs changes then would suggest using notifyItemChanged with payload. If the whole view type changes then notifyItemChanged without payload will work but will re-render the view giving a flicker effect. Here is the code.

adapter.notifyItemChanged(position, "CHANGE_TEXT_VIEW_ITEM");

And then in the RecyclerViewAdapter override onBindViewHolder which has payload with it.

@Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position, @NonNull List<Object> payloads) {
        if (payloads.isEmpty()) {
            super.onBindViewHolder(holder, position, payloads);
        } else {
            for (Object payload : payloads) {
                if (payload.equals("CHANGE_TEXT_VIEW_ITEM")) {
                    //you have holder and position to do relevant changes
                    }
            }
        }
    }

This will update the recycler items without any flicker. This can be achieved for notifyItemRangeChanged as well.

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
QuestionportfoliobuilderView Question on Stackoverflow
Solution 1 - AndroidNick CardosoView Answer on Stackoverflow
Solution 2 - AndroidBuddyView Answer on Stackoverflow
Solution 3 - AndroidMarian PaździochView Answer on Stackoverflow
Solution 4 - AndroidHarshit JainView Answer on Stackoverflow
Solution 5 - AndroidrosuhView Answer on Stackoverflow
Solution 6 - Androidtir38View Answer on Stackoverflow
Solution 7 - Androidkaran vsView Answer on Stackoverflow
Solution 8 - AndroidParth BhanushaliView Answer on Stackoverflow
Solution 9 - AndroidБогдан ДондукView Answer on Stackoverflow
Solution 10 - AndroidMohamed FarithView Answer on Stackoverflow