Disable onChange animations on ItemAnimator for RecyclerView
Asked Answered
S

3

16

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?

Surtout answered 3/3, 2016 at 8:8 Comment(0)
L
16

I'm a little late to the party on this one, but using androidx.recyclerview:recyclerview:1.1.0 I'm able to set the change the default animator's changeDuration to 0 which effectively disables the animation while allowing add/move/remove animations to continue running properly. No need for a custom override of DefaultItemAnimator.

Example (in Kotlin):

view.my_recycler_view.itemAnimator?.changeDuration = 0
Lynxeyed answered 27/5, 2020 at 13:6 Comment(0)
B
14

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!

Bellwort answered 23/3, 2017 at 7:9 Comment(0)
C
8

The above solution does not work for me with support library version of 25.3.1 as I want to disable all recycler view items' animation. I solved it by overriding SimpleItemAnimator:

private class NoAnimationItemAnimator extends SimpleItemAnimator {
    @Override
    public boolean animateRemove(RecyclerView.ViewHolder holder) {
        dispatchRemoveFinished(holder);

        return false;
    }

    @Override
    public boolean animateAdd(RecyclerView.ViewHolder holder) {
        dispatchAddFinished(holder);

        return false;
    }

    @Override
    public boolean animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) {
        dispatchMoveFinished(holder);

        return false;
    }

    @Override
    public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromX, int fromY, int toX, int toY) {
        dispatchChangeFinished(oldHolder, true);
        dispatchChangeFinished(newHolder, false);

        return false;
    }

    @Override
    public void runPendingAnimations() {
        // stub
    }

    @Override
    public void endAnimation(RecyclerView.ViewHolder item) {
        // stub
    }

    @Override
    public void endAnimations() {
        // stub
    }

    @Override
    public boolean isRunning() {
        return false;
    }
}
Crayfish answered 31/5, 2017 at 10:3 Comment(1)
well, it might work, however the question was related strictly to disabling only onChange & keeping other animationsBellwort

© 2022 - 2024 — McMap. All rights reserved.