问题
I have a RecycleView in which a display a list of items. I specify the default animator to the RecyclerView like this:
recyclerView.setItemAnimator( new DefaultItemAnimator() );
Everything works great, but I want to use my own custom animations for adding/removing/updating the elements in the list.
I defined a custom animator class like this:
public class MyAnimator extends RecyclerView.ItemAnimator {
@Override
public boolean animateDisappearance(@NonNull RecyclerView.ViewHolder viewHolder, @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {
return false;
}
@Override
public boolean animateAppearance(@NonNull RecyclerView.ViewHolder viewHolder, @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
return false;
}
@Override
public boolean animatePersistence(@NonNull RecyclerView.ViewHolder viewHolder, @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
return false;
}
@Override
public boolean animateChange(@NonNull RecyclerView.ViewHolder oldHolder, @NonNull RecyclerView.ViewHolder newHolder, @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
return false;
}
@Override
public void runPendingAnimations() {
}
@Override
public void endAnimation(RecyclerView.ViewHolder item) {
}
@Override
public void endAnimations() {
}
@Override
public boolean isRunning() {
return false;
}
}
And set it the same way as I did for DefaultItemAnimator. The animations are not playing anymore, so I guess it worked, but the problem is that items get stacked sometimes on top of each other, and when I remove all the items, some are still left, so I guess I'm missing something.
As far as I understood animateDisappearance is a method that gets called, when the item is removed from the list. If I return false, it should simply skip the animation as far as I understood, correct?
Am I even on the right track? when I look for examples of this on github, there are very few results and overall I can't seem to find any basic code example how to do this, and the ones I found are all thousands of lines of codes.
How can I simply overwrite the default add/remove animations with my own without using any external libraries? Thanks!
EDIT:
I was able to override the default animation the following way:
recyclerView.setItemAnimator(new DefaultItemAnimator() {
@Override
public boolean animateRemove(RecyclerView.ViewHolder holder) {
holder.itemView.clearAnimation();
final RecyclerView.ViewHolder h = holder;
holder.itemView.animate()
.alpha(0)
.setInterpolator(new AccelerateInterpolator(2.f))
.setDuration(1350)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
dispatchRemoveFinished(h);
}
})
.start();
//
return false;
}
} );
The animation works perfectly, but for some reason it seems like 'dispatchRemoveFinished' is triggered instantly, so instead of the remaining elements adjusting AFTER the animation, they do it instantly as soon as the view is removed. Is there any way to fix this?
回答1:
When implementing your RecyclerView.ItemAnimator
you have to follow few rules or RecyclerView state will get messed up:
All those empty methods returning false must at least call
dispatchAnimationFinished(viewHolder)
as well to clear animation state.If those methods are to start animation, You should
dispatchAnimationStarted(viewHolder)
, store animation request and return true to get call torunPendingAnimations()
where animations should actually begin.You need to keep track of ongoing animations to be able to properly cancel them, You will get requests for items that are already being animated as well.
Here's a sample ItemAnimator
that animates only removal and movement. Note the inner class that acts as data-holder of animations and listener of animation states:
public class RecAnimator extends RecyclerView.ItemAnimator {
private final static String TAG = "RecAnimator";
private final static int ANIMATION_TYPE_DISAPPEAR = 1;
private final static int ANIMATION_TYPE_MOVE = 2;
// must keep track of all pending/ongoing animations.
private final ArrayList<AnimInfo> pending = new ArrayList<>();
private final HashMap<RecyclerView.ViewHolder, AnimInfo> disappearances = new HashMap<>();
private final HashMap<RecyclerView.ViewHolder, AnimInfo> persistences = new HashMap<>();
@Override
public boolean animateDisappearance(@NonNull RecyclerView.ViewHolder viewHolder, @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {
pending.add(new AnimInfo(viewHolder, ANIMATION_TYPE_DISAPPEAR, 0));
dispatchAnimationStarted(viewHolder);
// new pending animation added, return true to indicate we want a call to runPendingAnimations()
return true;
}
@Override
public boolean animateAppearance(@NonNull RecyclerView.ViewHolder viewHolder, @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
dispatchAnimationFinished(viewHolder);
return false;
}
@Override
public boolean animatePersistence(@NonNull RecyclerView.ViewHolder viewHolder, @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
if (preLayoutInfo.top != postLayoutInfo.top) {
// required movement
int topDiff = preLayoutInfo.top - postLayoutInfo.top;
AnimInfo per = persistences.get(viewHolder);
if(per != null && per.isRunning) {
// there is already an ongoing animation - update it instead
per.top = per.holder.itemView.getTranslationY() + topDiff;
per.start();
// discard this animatePersistence call
dispatchAnimationFinished(viewHolder);
return false;
}
pending.add(new AnimInfo(viewHolder, ANIMATION_TYPE_MOVE, topDiff));
dispatchAnimationStarted(viewHolder);
// new pending animation added, return true to indicate we want a call to runPendingAnimations()
return true;
}
dispatchAnimationFinished(viewHolder);
return false;
}
@Override
public boolean animateChange(@NonNull RecyclerView.ViewHolder oldHolder, @NonNull RecyclerView.ViewHolder newHolder, @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
dispatchAnimationFinished(oldHolder);
dispatchAnimationFinished(newHolder);
return false;
}
@Override
public void runPendingAnimations() {
for (AnimInfo ai: pending) {
ai.start();
}
pending.clear();
}
@Override
public void endAnimation(RecyclerView.ViewHolder item) {
AnimInfo ai = disappearances.get(item);
if (ai != null && ai.isRunning) {
ai.holder.itemView.animate().cancel();
}
ai = persistences.get(item);
if (ai != null && ai.isRunning) {
ai.holder.itemView.animate().cancel();
}
}
@Override
public void endAnimations() {
for (AnimInfo ai: disappearances.values())
if (ai.isRunning)
ai.holder.itemView.animate().cancel();
for (AnimInfo ai: persistences.values())
if (ai.isRunning)
ai.holder.itemView.animate().cancel();
}
@Override
public boolean isRunning() {
return !pending.isEmpty() &&
!disappearances.isEmpty() &&
!persistences.isEmpty();
}
/**
* This is container for each animation. It's also cancel/end listener for them.
* */
private final class AnimInfo implements Animator.AnimatorListener {
private final RecyclerView.ViewHolder holder;
private final int animationType;
private float top;
private boolean isRunning = false;
private AnimInfo(RecyclerView.ViewHolder holder, int animationType, float top) {
this.holder = holder;
this.animationType = animationType;
this.top = top;
}
void start(){
View itemView = holder.itemView;
itemView.animate().setListener(this);
switch (animationType) {
case ANIMATION_TYPE_DISAPPEAR:
itemView.setPivotY(0f);
itemView.animate().scaleX(0f).scaleY(0f).setDuration(getRemoveDuration());
disappearances.put(holder, this); // must keep track of all animations
break;
case ANIMATION_TYPE_MOVE:
itemView.setTranslationY(top);
itemView.animate().translationY(0f).setDuration(getMoveDuration());
persistences.put(holder, this); // must keep track of all animations
break;
}
isRunning = true;
}
private void resetViewHolderState(){
// reset state as if no animation was ran
switch (animationType) {
case ANIMATION_TYPE_DISAPPEAR:
holder.itemView.setScaleX(1f);
holder.itemView.setScaleY(1f);
break;
case ANIMATION_TYPE_MOVE:
holder.itemView.setTranslationY(0f);
break;
}
}
@Override
public void onAnimationEnd(Animator animation) {
switch (animationType) {
case ANIMATION_TYPE_DISAPPEAR:
disappearances.remove(holder);
break;
case ANIMATION_TYPE_MOVE:
persistences.remove(holder);
break;
}
resetViewHolderState();
holder.itemView.animate().setListener(null); // clear listener
dispatchAnimationFinished(holder);
if (!isRunning())
dispatchAnimationsFinished();
isRunning = false;
}
@Override
public void onAnimationCancel(Animator animation) {
// jump to end state
switch (animationType) {
case ANIMATION_TYPE_DISAPPEAR:
holder.itemView.setScaleX(0f);
holder.itemView.setScaleY(0f);
break;
case ANIMATION_TYPE_MOVE:
holder.itemView.setTranslationY(0f);
break;
}
}
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
}
}
You can also override SimpleItemAnimator class that parses animate...
methods into animateMove
, animateRemove
etc. for you.
来源:https://stackoverflow.com/questions/51696530/properly-overwriting-recyclerview-animations