Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Disable onChange animations on ItemAnimator for RecyclerView

I am using a RecyclerView fed with data from a SortedList using a SortedListAdapterCallback. I want to disable animations for onChange events, but preserve them for onInserted/onRemoved/onMoved. I have tried calling setSupportsChangeAnimations(false) on the DefaultItemAnimator used by the RecyclerView, but the animation still appears. If I call setItemAnimator(null) all animations are successfully removed as expected though.

I tried looking at the implementation and it seems like if supportsChangeAnimations is true, the RecyclerView will animate change events by keeping the old viewHolder and cross-fade it to the new viewHolder. I don't want that. If supportsChangeAnimations is false, the old and new viewHolders will however be the same object, and there will instead be an onMoved animation from x to x (i.e., no actual move). This however means that the item will get an annoying bounce effect. I don't want that either, I want no animation at all. :(

From DefaultItemAnimator.java:

@Override
public boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder,
        int fromX, int fromY, int toX, int toY) {
    if (oldHolder == newHolder) {
        // Don't know how to run change animations when the same view holder is re-used.
        // run a move animation to handle position changes.
        return animateMove(oldHolder, fromX, fromY, toX, toY);
    }
    ...

Sometimes when I load my list I asynchronously fetch some data and update items 1-3 times, and it looks really crappy when it bounces and flickers every time.

How do I effectively completely disable onChange animations without resorting to writing a completely custom ItemAnimator?

like image 214
JHH Avatar asked Mar 03 '16 08:03

JHH


People also ask

What is itemanimator in Android recyclerview?

Android RecyclerView setItemAnimator (ItemAnimator animator) Sets the ItemAnimator that will handle animations involving changes to the items in this RecyclerView. Sets the ItemAnimator that will handle animations involving changes to the items in this RecyclerView. By default, RecyclerView instantiates and uses an instance of DefaultItemAnimator .

What is the default item animator for recyclerview?

This is the default one that is used by recyclerview and already handles animating adding, removing, moving items gracefully. For item change animations, DefaultItemAnimator uses cross-fade animations.

Should I use itemanimator inside the recyclerview adapter?

I recently worked on a project for kids which uses lots of animations, and there were also cases where a view within the recyclerview item needs to be animated. I had previously watched this great talk, and I recalled that I shouldn’t do this inside the adapter but should instead use a custom ItemAnimator.

What is defaultitemanimator in recyclerview?

This is the default one that is used by recyclerview and already handles animating adding, removing, moving items gracefully. For item change animations, DefaultItemAnimator uses cross-fade animations. This makes sense, as recyclerview doesn’t know what will change exactly.


1 Answers

Looking through the code (I'm using support library 25.2.0): setSupportsChangeAnimations(<value>) is a method on the abstract class SimpleItemAnimator, which is also DefaultItemAnimator's superclass. Internally, it modifies the value of mSupportsChangeAnimations.

Performing a text search in DefaultItemAnimator's code, reveals that neither mSupportsChangeAnimations, nor getSupportsChangeAnimations() are queried --> the DefaultItemAnimator literally ignores this flag.

The correct solution is to extend the DefaultItemAnimator in the following manner:

public class CustomItemAnimator extends DefaultItemAnimator {
@Override
public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromX, int fromY, int toX, int toY) {
    if (getSupportsChangeAnimations()) {
        return super.animateChange(oldHolder, newHolder, fromX, fromY, toX, toY);
    } else {
        if (oldHolder == newHolder) {
            if (oldHolder != null) {
                //if the two holders are equal, call dispatch change only once
                dispatchChangeFinished(oldHolder, /*ignored*/true);
            }
        } else {
            //else call dispatch change once for every non-null holder
            if (oldHolder != null) {
                dispatchChangeFinished(oldHolder, true);
            }
            if (newHolder != null) {
                dispatchChangeFinished(newHolder, false);
            }
        }
        //we don't need a call to requestPendingTransactions after this, return false.
        return false;
    }
}

See docs animateChange(...) to understand why it was needed to call dispatchChangeFinished(...) when no animations were run.

Probably there's a more elegant way to write the else branch when there are no animations to be run, but alas, this achieves the desired behavior.

Kind'of late, but hope this helps!

like image 140
cjurjiu Avatar answered Oct 19 '22 03:10

cjurjiu