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