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?
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 .
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.
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.
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.
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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With