Reputation: 9295
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?
Upvotes: 16
Views: 12313
Reputation: 824
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
Upvotes: 16
Reputation: 6011
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;
}
}
Upvotes: 8
Reputation: 1788
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!
Upvotes: 14