我們經常會遇到在一個list中刪除一條數據,這時候一般會有一個飛出的動畫效果,如下圖:
在RecyclerView中可以通過setItemAnimator函數設置一個ItemAnimator,實現item的add、remove、change等動作的動效。下面我們就通過ItemAnimator來實現上面的效果。
首先創建一個類,繼承至SimpleItemAnimator,如下:
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;
}
}
SimpleItemAnimator是一個抽象類,需要實現幾個函數。
因爲我們要實現是一個remove的動作,需要在animateRemove中處理。這裏我們參照DefaultItemAnimator的做法,首先需要兩個list,然後在animateRemove將holder添加進list中,這裏暫時不做處理,如下:
List<RecyclerView.ViewHolder> removeHolders = new ArrayList<>();
List<RecyclerView.ViewHolder> removeAnimators = new ArrayList<>();
@Override
public boolean animateRemove(RecyclerView.ViewHolder holder) {
removeHolders.add(holder);
return true;
}
至於另外一個list下面會用到。
既然我們在animateRemove函數中不做動效處理,那麼應該在哪裏處理?
答案是在runPedingAnimations中來處理,代碼如下:
@Override
public void runPendingAnimations() {
if(!removeHolders.isEmpty()) {
for(RecyclerView.ViewHolder holder : removeHolders) {
remove(holder);
}
removeHolders.clear();
}
}
遍歷removeHolders,依次執行remove,這個函數是自定義的,用於執行動畫,代碼如下:
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);
}
可以看到就是對holder的itemview執行來一個移動動畫。
這裏使用removeAnimators來管理所有的remove動畫,目前是判斷所有的remove動畫是否結束,這個判斷在isRunning函數中,代碼如下:
@Override
public boolean isRunning() {
return !(removeHolders.isEmpty() && removeAnimators.isEmpty());
}
當兩個list都爲空的時候,所有動畫都完成了,回到remove代碼中這時候執行disPatchAnimationsFinished函數。
通過上面幾步,實現了remove的動效,當我們執行的時候發現確實有了飛出的效果,但是下面的item卻瞬間上移導致重疊。效果如下:
這時因爲我們目前只定義了remove的效果,實際上不僅有飛出的動作還有一個上移的動作,所以還需要定義一下move的效果,同remove一樣需要兩個list,在animateMove函數中將holder添加至list中,如下:
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;
}
注意在remove的一瞬間,下方的item實際上就已經上移了,所以在animateMove中設置item的translationY使其保持在未上移的位置。
然後同樣在runPedingAnimations中處理,這時runPedingAnimations代碼如下:
@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();
}
}
這裏move同樣是自定義的一個函數,代碼如下:
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();
}
執行了一個屬性動畫,修改了item的translationY使其上移回原位置。同時注意修改isRunning函數,如下:
@Override
public boolean isRunning() {
return !(removeHolders.isEmpty() && removeAnimators.isEmpty() && moveHolders.isEmpty() && moveAnimators.isEmpty());
}
這樣就實現了一開始的飛出效果。
總結一下,其實自定義ItemAnimator比較簡單,雖然代碼接近百行,但其實主要就是執行動畫。需要注意的就是有些情況需要配合move的動作。
自定義ItemAnimator後,直接爲RecyclerView設置即可:
list.setItemAnimator(new FlyAnimator());
設置後如果調用了adapter的notifyItemRemoved函數就會執行remove的動效。
完整代碼如下:
class FlyAnimator extends SimpleItemAnimator{
List<RecyclerView.ViewHolder> removeHolders = new ArrayList<>();
List<RecyclerView.ViewHolder> removeAnimators = new ArrayList<>();
List<RecyclerView.ViewHolder> moveHolders = new ArrayList<>();
List<RecyclerView.ViewHolder> moveAnimators = new ArrayList<>();
@Override
public boolean animateRemove(RecyclerView.ViewHolder holder) {
removeHolders.add(holder);
return true;
}
@Override
public boolean animateAdd(RecyclerView.ViewHolder holder) {
return false;
}
@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;
}
@Override
public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop) {
return false;
}
@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();
}
}
@Override
public void endAnimation(RecyclerView.ViewHolder item) {
}
@Override
public void endAnimations() {
}
@Override
public boolean isRunning() {
return !(removeHolders.isEmpty() && removeAnimators.isEmpty() && moveHolders.isEmpty() && moveAnimators.isEmpty());
}
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);
}
private void move(final RecyclerView.ViewHolder holder){
moveAnimators.add(holder);
ObjectAnimator animator = ObjectAnimator.ofFloat(holder.itemView,
"translationY", holder.itemView.getTranslationY(), 0);
animator.setDuration(500);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(android.animation.Animator animation) {
dispatchMoveStarting(holder);
}
@Override
public void onAnimationEnd(android.animation.Animator animation) {
dispatchMoveFinished(holder);
moveAnimators.remove(holder);
if(!isRunning()) dispatchAnimationsFinished();
}
});
animator.start();
}
}