
We often encounter deleting a piece of data in a list. At this time, there will be a flying animation effect, as shown in the following figure:

In RecyclerView, you can set an ItemAnimator through setItemAnimator function to realize the dynamic effect of add, remove, change and other actions of item. Let's use ItemAnimator to achieve the above effect.
Create ItemAnimator
First create a class and inherit it to Simple ItemAnimator, as follows:
class FlyAnimator extends SimpleItemAnimator{ @Override public boolean animateRemove(RecyclerView.ViewHolder holder) { return false; } @Override public boolean animateAdd(RecyclerView.ViewHolder holder) { return false; } @Override public boolean animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) { return false; } @Override public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop) { return false; } @Override public void runPendingAnimations() { } @Override public void endAnimation(RecyclerView.ViewHolder item) { } @Override public void endAnimations() { } @Override public boolean isRunning() { return false; } }
Simple ItemAnimator is an abstract class that needs to implement several functions.
Because we want to implement a remove action, it needs to be processed in animateRemove. Here, we refer to the DefaultItemAnimator. First, we need two lists, and then add the holder to the list in animateRemove. It will not be processed here for the time being, as follows:
List<RecyclerView.ViewHolder> removeHolders = new ArrayList<>(); List<RecyclerView.ViewHolder> removeAnimators = new ArrayList<>(); @Override public boolean animateRemove(RecyclerView.ViewHolder holder) { removeHolders.add(holder); return true; }
As for another list, it will be used below.
Since we do not do dynamic effect processing in animateRemove function, where should we handle it?
The answer is processed in runPedingAnimations. The code is as follows:
@Override public void runPendingAnimations() { if(!removeHolders.isEmpty()) { for(RecyclerView.ViewHolder holder : removeHolders) { remove(holder); } removeHolders.clear(); } }
Traverse removeHolders and execute remove in turn. This function is user-defined and used to execute animation. The code is as follows:
private void remove(final RecyclerView.ViewHolder holder){ removeAnimators.add(holder); TranslateAnimation animation = new TranslateAnimation(0, 1000, 0, 0); animation.setDuration(500); animation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { dispatchRemoveStarting(holder); } @Override public void onAnimationEnd(Animation animation) { removeAnimators.remove(holder); dispatchRemoveFinished(holder); if(!isRunning()){ dispatchAnimationsFinished(); } } @Override public void onAnimationRepeat(Animation animation) { } }); holder.itemView.startAnimation(animation); }
You can see that it is to perform a moving animation on the holder's itemview.
Here, removeAnimators are used to manage all remove animations. At present, it is to judge whether all remove animations are finished. This judgment is in the isRunning function, and the code is as follows:
@Override public boolean isRunning() { return !(removeHolders.isEmpty() && removeAnimators.isEmpty()); }
When both list s are empty, all animations are completed. Return to the remove code. At this time, execute the dispatchanimationsinished function.
Through the above steps, the dynamic effect of remove is realized. When we execute it, we find that there is indeed a flying effect, but the following item s are instantly moved up, resulting in overlap. The effects are as follows:

Process overlap
This is because we only define the effect of remove. In fact, there is not only a fly out action, but also a move up action. Therefore, we also need to define the effect of move. Like remove, we need two lists. Add the holder to the list in the animateMove function, as follows:
List<RecyclerView.ViewHolder> moveHolders = new ArrayList<>(); List<RecyclerView.ViewHolder> moveAnimators = new ArrayList<>(); @Override public boolean animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) { holder.itemView.setTranslationY(fromY - toY); moveHolders.add(holder); return true; }
Note that at the moment of remove, the lower item has actually moved up, so set the item's translationY in animateMove to keep it in the non moved position.
Then it is also processed in runPedingAnimations. At this time, the runPedingAnimations code is as follows:
@Override public void runPendingAnimations() { if(!removeHolders.isEmpty()) { for(RecyclerView.ViewHolder holder : removeHolders) { remove(holder); } removeHolders.clear(); } if(!moveHolders.isEmpty()){ for(RecyclerView.ViewHolder holder : moveHolders) { move(holder); } moveHolders.clear(); } }
Here, move is also a user-defined function. The code is as follows:
private void move(final MoveInfo moveInfo){ moveAnimators.add(moveInfo); ObjectAnimator animator = ObjectAnimator.ofFloat(moveInfo.holder.itemView, "translationY", moveInfo.holder.itemView.getTranslationY(), 0); animator.setDuration(500); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(android.animation.Animator animation) { dispatchMoveStarting(moveInfo.holder); } @Override public void onAnimationEnd(android.animation.Animator animation) { dispatchMoveFinished(moveInfo.holder); moveAnimators.remove(moveInfo.holder); if(!isRunning()) dispatchAnimationsFinished(); } }); animator.start(); }
An attribute animation was executed, and the item's translationY was modified to move it up and back to its original position. At the same time, pay attention to modifying the isRunning function as follows:
@Override public boolean isRunning() { return !(removeHolders.isEmpty() && removeAnimators.isEmpty() && moveHolders.isEmpty() && moveAnimators.isEmpty()); }
summary
To sum up, in fact, customizing ItemAnimator is relatively simple. Although the code is close to 100 lines, it is mainly to execute animation. It should be noted that some situations need to cooperate with the move action.
After customizing ItemAnimator, you can directly set it for RecyclerView:
list.setItemAnimator(new FlyAnimator());
After setting, if the notifyItemRemoved function of the adapter is called, the remove action will be executed.