Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.internal.widget;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.TimeInterpolator;
     22 import android.animation.ValueAnimator;
     23 import android.annotation.NonNull;
     24 import android.view.View;
     25 import android.view.ViewPropertyAnimator;
     26 
     27 import com.android.internal.widget.RecyclerView.ViewHolder;
     28 
     29 import java.util.ArrayList;
     30 import java.util.List;
     31 
     32 /**
     33  * This implementation of {@link RecyclerView.ItemAnimator} provides basic
     34  * animations on remove, add, and move events that happen to the items in
     35  * a RecyclerView. RecyclerView uses a DefaultItemAnimator by default.
     36  *
     37  * @see RecyclerView#setItemAnimator(RecyclerView.ItemAnimator)
     38  */
     39 public class DefaultItemAnimator extends SimpleItemAnimator {
     40     private static final boolean DEBUG = false;
     41 
     42     private static TimeInterpolator sDefaultInterpolator;
     43 
     44     private ArrayList<ViewHolder> mPendingRemovals = new ArrayList<>();
     45     private ArrayList<ViewHolder> mPendingAdditions = new ArrayList<>();
     46     private ArrayList<MoveInfo> mPendingMoves = new ArrayList<>();
     47     private ArrayList<ChangeInfo> mPendingChanges = new ArrayList<>();
     48 
     49     ArrayList<ArrayList<ViewHolder>> mAdditionsList = new ArrayList<>();
     50     ArrayList<ArrayList<MoveInfo>> mMovesList = new ArrayList<>();
     51     ArrayList<ArrayList<ChangeInfo>> mChangesList = new ArrayList<>();
     52 
     53     ArrayList<ViewHolder> mAddAnimations = new ArrayList<>();
     54     ArrayList<ViewHolder> mMoveAnimations = new ArrayList<>();
     55     ArrayList<ViewHolder> mRemoveAnimations = new ArrayList<>();
     56     ArrayList<ViewHolder> mChangeAnimations = new ArrayList<>();
     57 
     58     private static class MoveInfo {
     59         public ViewHolder holder;
     60         public int fromX, fromY, toX, toY;
     61 
     62         MoveInfo(ViewHolder holder, int fromX, int fromY, int toX, int toY) {
     63             this.holder = holder;
     64             this.fromX = fromX;
     65             this.fromY = fromY;
     66             this.toX = toX;
     67             this.toY = toY;
     68         }
     69     }
     70 
     71     private static class ChangeInfo {
     72         public ViewHolder oldHolder, newHolder;
     73         public int fromX, fromY, toX, toY;
     74         private ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder) {
     75             this.oldHolder = oldHolder;
     76             this.newHolder = newHolder;
     77         }
     78 
     79         ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder,
     80                 int fromX, int fromY, int toX, int toY) {
     81             this(oldHolder, newHolder);
     82             this.fromX = fromX;
     83             this.fromY = fromY;
     84             this.toX = toX;
     85             this.toY = toY;
     86         }
     87 
     88         @Override
     89         public String toString() {
     90             return "ChangeInfo{"
     91                     + "oldHolder=" + oldHolder
     92                     + ", newHolder=" + newHolder
     93                     + ", fromX=" + fromX
     94                     + ", fromY=" + fromY
     95                     + ", toX=" + toX
     96                     + ", toY=" + toY
     97                     + '}';
     98         }
     99     }
    100 
    101     @Override
    102     public void runPendingAnimations() {
    103         boolean removalsPending = !mPendingRemovals.isEmpty();
    104         boolean movesPending = !mPendingMoves.isEmpty();
    105         boolean changesPending = !mPendingChanges.isEmpty();
    106         boolean additionsPending = !mPendingAdditions.isEmpty();
    107         if (!removalsPending && !movesPending && !additionsPending && !changesPending) {
    108             // nothing to animate
    109             return;
    110         }
    111         // First, remove stuff
    112         for (ViewHolder holder : mPendingRemovals) {
    113             animateRemoveImpl(holder);
    114         }
    115         mPendingRemovals.clear();
    116         // Next, move stuff
    117         if (movesPending) {
    118             final ArrayList<MoveInfo> moves = new ArrayList<>();
    119             moves.addAll(mPendingMoves);
    120             mMovesList.add(moves);
    121             mPendingMoves.clear();
    122             Runnable mover = new Runnable() {
    123                 @Override
    124                 public void run() {
    125                     for (MoveInfo moveInfo : moves) {
    126                         animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY,
    127                                 moveInfo.toX, moveInfo.toY);
    128                     }
    129                     moves.clear();
    130                     mMovesList.remove(moves);
    131                 }
    132             };
    133             if (removalsPending) {
    134                 View view = moves.get(0).holder.itemView;
    135                 view.postOnAnimationDelayed(mover, getRemoveDuration());
    136             } else {
    137                 mover.run();
    138             }
    139         }
    140         // Next, change stuff, to run in parallel with move animations
    141         if (changesPending) {
    142             final ArrayList<ChangeInfo> changes = new ArrayList<>();
    143             changes.addAll(mPendingChanges);
    144             mChangesList.add(changes);
    145             mPendingChanges.clear();
    146             Runnable changer = new Runnable() {
    147                 @Override
    148                 public void run() {
    149                     for (ChangeInfo change : changes) {
    150                         animateChangeImpl(change);
    151                     }
    152                     changes.clear();
    153                     mChangesList.remove(changes);
    154                 }
    155             };
    156             if (removalsPending) {
    157                 ViewHolder holder = changes.get(0).oldHolder;
    158                 holder.itemView.postOnAnimationDelayed(changer, getRemoveDuration());
    159             } else {
    160                 changer.run();
    161             }
    162         }
    163         // Next, add stuff
    164         if (additionsPending) {
    165             final ArrayList<ViewHolder> additions = new ArrayList<>();
    166             additions.addAll(mPendingAdditions);
    167             mAdditionsList.add(additions);
    168             mPendingAdditions.clear();
    169             Runnable adder = new Runnable() {
    170                 @Override
    171                 public void run() {
    172                     for (ViewHolder holder : additions) {
    173                         animateAddImpl(holder);
    174                     }
    175                     additions.clear();
    176                     mAdditionsList.remove(additions);
    177                 }
    178             };
    179             if (removalsPending || movesPending || changesPending) {
    180                 long removeDuration = removalsPending ? getRemoveDuration() : 0;
    181                 long moveDuration = movesPending ? getMoveDuration() : 0;
    182                 long changeDuration = changesPending ? getChangeDuration() : 0;
    183                 long totalDelay = removeDuration + Math.max(moveDuration, changeDuration);
    184                 View view = additions.get(0).itemView;
    185                 view.postOnAnimationDelayed(adder, totalDelay);
    186             } else {
    187                 adder.run();
    188             }
    189         }
    190     }
    191 
    192     @Override
    193     public boolean animateRemove(final ViewHolder holder) {
    194         resetAnimation(holder);
    195         mPendingRemovals.add(holder);
    196         return true;
    197     }
    198 
    199     private void animateRemoveImpl(final ViewHolder holder) {
    200         final View view = holder.itemView;
    201         final ViewPropertyAnimator animation = view.animate();
    202         mRemoveAnimations.add(holder);
    203         animation.setDuration(getRemoveDuration()).alpha(0).setListener(
    204                 new AnimatorListenerAdapter() {
    205                     @Override
    206                     public void onAnimationStart(Animator animator) {
    207                         dispatchRemoveStarting(holder);
    208                     }
    209 
    210                     @Override
    211                     public void onAnimationEnd(Animator animator) {
    212                         animation.setListener(null);
    213                         view.setAlpha(1);
    214                         dispatchRemoveFinished(holder);
    215                         mRemoveAnimations.remove(holder);
    216                         dispatchFinishedWhenDone();
    217                     }
    218                 }).start();
    219     }
    220 
    221     @Override
    222     public boolean animateAdd(final ViewHolder holder) {
    223         resetAnimation(holder);
    224         holder.itemView.setAlpha(0);
    225         mPendingAdditions.add(holder);
    226         return true;
    227     }
    228 
    229     void animateAddImpl(final ViewHolder holder) {
    230         final View view = holder.itemView;
    231         final ViewPropertyAnimator animation = view.animate();
    232         mAddAnimations.add(holder);
    233         animation.alpha(1).setDuration(getAddDuration())
    234                 .setListener(new AnimatorListenerAdapter() {
    235                     @Override
    236                     public void onAnimationStart(Animator animator) {
    237                         dispatchAddStarting(holder);
    238                     }
    239 
    240                     @Override
    241                     public void onAnimationCancel(Animator animator) {
    242                         view.setAlpha(1);
    243                     }
    244 
    245                     @Override
    246                     public void onAnimationEnd(Animator animator) {
    247                         animation.setListener(null);
    248                         dispatchAddFinished(holder);
    249                         mAddAnimations.remove(holder);
    250                         dispatchFinishedWhenDone();
    251                     }
    252                 }).start();
    253     }
    254 
    255     @Override
    256     public boolean animateMove(final ViewHolder holder, int fromX, int fromY,
    257             int toX, int toY) {
    258         final View view = holder.itemView;
    259         fromX += holder.itemView.getTranslationX();
    260         fromY += holder.itemView.getTranslationY();
    261         resetAnimation(holder);
    262         int deltaX = toX - fromX;
    263         int deltaY = toY - fromY;
    264         if (deltaX == 0 && deltaY == 0) {
    265             dispatchMoveFinished(holder);
    266             return false;
    267         }
    268         if (deltaX != 0) {
    269             view.setTranslationX(-deltaX);
    270         }
    271         if (deltaY != 0) {
    272             view.setTranslationY(-deltaY);
    273         }
    274         mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY));
    275         return true;
    276     }
    277 
    278     void animateMoveImpl(final ViewHolder holder, int fromX, int fromY, int toX, int toY) {
    279         final View view = holder.itemView;
    280         final int deltaX = toX - fromX;
    281         final int deltaY = toY - fromY;
    282         if (deltaX != 0) {
    283             view.animate().translationX(0);
    284         }
    285         if (deltaY != 0) {
    286             view.animate().translationY(0);
    287         }
    288         // TODO: make EndActions end listeners instead, since end actions aren't called when
    289         // vpas are canceled (and can't end them. why?)
    290         // need listener functionality in VPACompat for this. Ick.
    291         final ViewPropertyAnimator animation = view.animate();
    292         mMoveAnimations.add(holder);
    293         animation.setDuration(getMoveDuration()).setListener(new AnimatorListenerAdapter() {
    294             @Override
    295             public void onAnimationStart(Animator animator) {
    296                 dispatchMoveStarting(holder);
    297             }
    298 
    299             @Override
    300             public void onAnimationCancel(Animator animator) {
    301                 if (deltaX != 0) {
    302                     view.setTranslationX(0);
    303                 }
    304                 if (deltaY != 0) {
    305                     view.setTranslationY(0);
    306                 }
    307             }
    308 
    309             @Override
    310             public void onAnimationEnd(Animator animator) {
    311                 animation.setListener(null);
    312                 dispatchMoveFinished(holder);
    313                 mMoveAnimations.remove(holder);
    314                 dispatchFinishedWhenDone();
    315             }
    316         }).start();
    317     }
    318 
    319     @Override
    320     public boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder,
    321             int fromX, int fromY, int toX, int toY) {
    322         if (oldHolder == newHolder) {
    323             // Don't know how to run change animations when the same view holder is re-used.
    324             // run a move animation to handle position changes.
    325             return animateMove(oldHolder, fromX, fromY, toX, toY);
    326         }
    327         final float prevTranslationX = oldHolder.itemView.getTranslationX();
    328         final float prevTranslationY = oldHolder.itemView.getTranslationY();
    329         final float prevAlpha = oldHolder.itemView.getAlpha();
    330         resetAnimation(oldHolder);
    331         int deltaX = (int) (toX - fromX - prevTranslationX);
    332         int deltaY = (int) (toY - fromY - prevTranslationY);
    333         // recover prev translation state after ending animation
    334         oldHolder.itemView.setTranslationX(prevTranslationX);
    335         oldHolder.itemView.setTranslationY(prevTranslationY);
    336         oldHolder.itemView.setAlpha(prevAlpha);
    337         if (newHolder != null) {
    338             // carry over translation values
    339             resetAnimation(newHolder);
    340             newHolder.itemView.setTranslationX(-deltaX);
    341             newHolder.itemView.setTranslationY(-deltaY);
    342             newHolder.itemView.setAlpha(0);
    343         }
    344         mPendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY));
    345         return true;
    346     }
    347 
    348     void animateChangeImpl(final ChangeInfo changeInfo) {
    349         final ViewHolder holder = changeInfo.oldHolder;
    350         final View view = holder == null ? null : holder.itemView;
    351         final ViewHolder newHolder = changeInfo.newHolder;
    352         final View newView = newHolder != null ? newHolder.itemView : null;
    353         if (view != null) {
    354             final ViewPropertyAnimator oldViewAnim = view.animate().setDuration(
    355                     getChangeDuration());
    356             mChangeAnimations.add(changeInfo.oldHolder);
    357             oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX);
    358             oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY);
    359             oldViewAnim.alpha(0).setListener(new AnimatorListenerAdapter() {
    360                 @Override
    361                 public void onAnimationStart(Animator animator) {
    362                     dispatchChangeStarting(changeInfo.oldHolder, true);
    363                 }
    364 
    365                 @Override
    366                 public void onAnimationEnd(Animator animator) {
    367                     oldViewAnim.setListener(null);
    368                     view.setAlpha(1);
    369                     view.setTranslationX(0);
    370                     view.setTranslationY(0);
    371                     dispatchChangeFinished(changeInfo.oldHolder, true);
    372                     mChangeAnimations.remove(changeInfo.oldHolder);
    373                     dispatchFinishedWhenDone();
    374                 }
    375             }).start();
    376         }
    377         if (newView != null) {
    378             final ViewPropertyAnimator newViewAnimation = newView.animate();
    379             mChangeAnimations.add(changeInfo.newHolder);
    380             newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration())
    381                     .alpha(1).setListener(new AnimatorListenerAdapter() {
    382                         @Override
    383                         public void onAnimationStart(Animator animator) {
    384                             dispatchChangeStarting(changeInfo.newHolder, false);
    385                         }
    386                         @Override
    387                         public void onAnimationEnd(Animator animator) {
    388                             newViewAnimation.setListener(null);
    389                             newView.setAlpha(1);
    390                             newView.setTranslationX(0);
    391                             newView.setTranslationY(0);
    392                             dispatchChangeFinished(changeInfo.newHolder, false);
    393                             mChangeAnimations.remove(changeInfo.newHolder);
    394                             dispatchFinishedWhenDone();
    395                         }
    396                     }).start();
    397         }
    398     }
    399 
    400     private void endChangeAnimation(List<ChangeInfo> infoList, ViewHolder item) {
    401         for (int i = infoList.size() - 1; i >= 0; i--) {
    402             ChangeInfo changeInfo = infoList.get(i);
    403             if (endChangeAnimationIfNecessary(changeInfo, item)) {
    404                 if (changeInfo.oldHolder == null && changeInfo.newHolder == null) {
    405                     infoList.remove(changeInfo);
    406                 }
    407             }
    408         }
    409     }
    410 
    411     private void endChangeAnimationIfNecessary(ChangeInfo changeInfo) {
    412         if (changeInfo.oldHolder != null) {
    413             endChangeAnimationIfNecessary(changeInfo, changeInfo.oldHolder);
    414         }
    415         if (changeInfo.newHolder != null) {
    416             endChangeAnimationIfNecessary(changeInfo, changeInfo.newHolder);
    417         }
    418     }
    419     private boolean endChangeAnimationIfNecessary(ChangeInfo changeInfo, ViewHolder item) {
    420         boolean oldItem = false;
    421         if (changeInfo.newHolder == item) {
    422             changeInfo.newHolder = null;
    423         } else if (changeInfo.oldHolder == item) {
    424             changeInfo.oldHolder = null;
    425             oldItem = true;
    426         } else {
    427             return false;
    428         }
    429         item.itemView.setAlpha(1);
    430         item.itemView.setTranslationX(0);
    431         item.itemView.setTranslationY(0);
    432         dispatchChangeFinished(item, oldItem);
    433         return true;
    434     }
    435 
    436     @Override
    437     public void endAnimation(ViewHolder item) {
    438         final View view = item.itemView;
    439         // this will trigger end callback which should set properties to their target values.
    440         view.animate().cancel();
    441         // TODO if some other animations are chained to end, how do we cancel them as well?
    442         for (int i = mPendingMoves.size() - 1; i >= 0; i--) {
    443             MoveInfo moveInfo = mPendingMoves.get(i);
    444             if (moveInfo.holder == item) {
    445                 view.setTranslationY(0);
    446                 view.setTranslationX(0);
    447                 dispatchMoveFinished(item);
    448                 mPendingMoves.remove(i);
    449             }
    450         }
    451         endChangeAnimation(mPendingChanges, item);
    452         if (mPendingRemovals.remove(item)) {
    453             view.setAlpha(1);
    454             dispatchRemoveFinished(item);
    455         }
    456         if (mPendingAdditions.remove(item)) {
    457             view.setAlpha(1);
    458             dispatchAddFinished(item);
    459         }
    460 
    461         for (int i = mChangesList.size() - 1; i >= 0; i--) {
    462             ArrayList<ChangeInfo> changes = mChangesList.get(i);
    463             endChangeAnimation(changes, item);
    464             if (changes.isEmpty()) {
    465                 mChangesList.remove(i);
    466             }
    467         }
    468         for (int i = mMovesList.size() - 1; i >= 0; i--) {
    469             ArrayList<MoveInfo> moves = mMovesList.get(i);
    470             for (int j = moves.size() - 1; j >= 0; j--) {
    471                 MoveInfo moveInfo = moves.get(j);
    472                 if (moveInfo.holder == item) {
    473                     view.setTranslationY(0);
    474                     view.setTranslationX(0);
    475                     dispatchMoveFinished(item);
    476                     moves.remove(j);
    477                     if (moves.isEmpty()) {
    478                         mMovesList.remove(i);
    479                     }
    480                     break;
    481                 }
    482             }
    483         }
    484         for (int i = mAdditionsList.size() - 1; i >= 0; i--) {
    485             ArrayList<ViewHolder> additions = mAdditionsList.get(i);
    486             if (additions.remove(item)) {
    487                 view.setAlpha(1);
    488                 dispatchAddFinished(item);
    489                 if (additions.isEmpty()) {
    490                     mAdditionsList.remove(i);
    491                 }
    492             }
    493         }
    494 
    495         // animations should be ended by the cancel above.
    496         //noinspection PointlessBooleanExpression,ConstantConditions
    497         if (mRemoveAnimations.remove(item) && DEBUG) {
    498             throw new IllegalStateException("after animation is cancelled, item should not be in "
    499                     + "mRemoveAnimations list");
    500         }
    501 
    502         //noinspection PointlessBooleanExpression,ConstantConditions
    503         if (mAddAnimations.remove(item) && DEBUG) {
    504             throw new IllegalStateException("after animation is cancelled, item should not be in "
    505                     + "mAddAnimations list");
    506         }
    507 
    508         //noinspection PointlessBooleanExpression,ConstantConditions
    509         if (mChangeAnimations.remove(item) && DEBUG) {
    510             throw new IllegalStateException("after animation is cancelled, item should not be in "
    511                     + "mChangeAnimations list");
    512         }
    513 
    514         //noinspection PointlessBooleanExpression,ConstantConditions
    515         if (mMoveAnimations.remove(item) && DEBUG) {
    516             throw new IllegalStateException("after animation is cancelled, item should not be in "
    517                     + "mMoveAnimations list");
    518         }
    519         dispatchFinishedWhenDone();
    520     }
    521 
    522     private void resetAnimation(ViewHolder holder) {
    523         if (sDefaultInterpolator == null) {
    524             sDefaultInterpolator = new ValueAnimator().getInterpolator();
    525         }
    526         holder.itemView.animate().setInterpolator(sDefaultInterpolator);
    527         endAnimation(holder);
    528     }
    529 
    530     @Override
    531     public boolean isRunning() {
    532         return (!mPendingAdditions.isEmpty()
    533                 || !mPendingChanges.isEmpty()
    534                 || !mPendingMoves.isEmpty()
    535                 || !mPendingRemovals.isEmpty()
    536                 || !mMoveAnimations.isEmpty()
    537                 || !mRemoveAnimations.isEmpty()
    538                 || !mAddAnimations.isEmpty()
    539                 || !mChangeAnimations.isEmpty()
    540                 || !mMovesList.isEmpty()
    541                 || !mAdditionsList.isEmpty()
    542                 || !mChangesList.isEmpty());
    543     }
    544 
    545     /**
    546      * Check the state of currently pending and running animations. If there are none
    547      * pending/running, call {@link #dispatchAnimationsFinished()} to notify any
    548      * listeners.
    549      */
    550     void dispatchFinishedWhenDone() {
    551         if (!isRunning()) {
    552             dispatchAnimationsFinished();
    553         }
    554     }
    555 
    556     @Override
    557     public void endAnimations() {
    558         int count = mPendingMoves.size();
    559         for (int i = count - 1; i >= 0; i--) {
    560             MoveInfo item = mPendingMoves.get(i);
    561             View view = item.holder.itemView;
    562             view.setTranslationY(0);
    563             view.setTranslationX(0);
    564             dispatchMoveFinished(item.holder);
    565             mPendingMoves.remove(i);
    566         }
    567         count = mPendingRemovals.size();
    568         for (int i = count - 1; i >= 0; i--) {
    569             ViewHolder item = mPendingRemovals.get(i);
    570             dispatchRemoveFinished(item);
    571             mPendingRemovals.remove(i);
    572         }
    573         count = mPendingAdditions.size();
    574         for (int i = count - 1; i >= 0; i--) {
    575             ViewHolder item = mPendingAdditions.get(i);
    576             item.itemView.setAlpha(1);
    577             dispatchAddFinished(item);
    578             mPendingAdditions.remove(i);
    579         }
    580         count = mPendingChanges.size();
    581         for (int i = count - 1; i >= 0; i--) {
    582             endChangeAnimationIfNecessary(mPendingChanges.get(i));
    583         }
    584         mPendingChanges.clear();
    585         if (!isRunning()) {
    586             return;
    587         }
    588 
    589         int listCount = mMovesList.size();
    590         for (int i = listCount - 1; i >= 0; i--) {
    591             ArrayList<MoveInfo> moves = mMovesList.get(i);
    592             count = moves.size();
    593             for (int j = count - 1; j >= 0; j--) {
    594                 MoveInfo moveInfo = moves.get(j);
    595                 ViewHolder item = moveInfo.holder;
    596                 View view = item.itemView;
    597                 view.setTranslationY(0);
    598                 view.setTranslationX(0);
    599                 dispatchMoveFinished(moveInfo.holder);
    600                 moves.remove(j);
    601                 if (moves.isEmpty()) {
    602                     mMovesList.remove(moves);
    603                 }
    604             }
    605         }
    606         listCount = mAdditionsList.size();
    607         for (int i = listCount - 1; i >= 0; i--) {
    608             ArrayList<ViewHolder> additions = mAdditionsList.get(i);
    609             count = additions.size();
    610             for (int j = count - 1; j >= 0; j--) {
    611                 ViewHolder item = additions.get(j);
    612                 View view = item.itemView;
    613                 view.setAlpha(1);
    614                 dispatchAddFinished(item);
    615                 additions.remove(j);
    616                 if (additions.isEmpty()) {
    617                     mAdditionsList.remove(additions);
    618                 }
    619             }
    620         }
    621         listCount = mChangesList.size();
    622         for (int i = listCount - 1; i >= 0; i--) {
    623             ArrayList<ChangeInfo> changes = mChangesList.get(i);
    624             count = changes.size();
    625             for (int j = count - 1; j >= 0; j--) {
    626                 endChangeAnimationIfNecessary(changes.get(j));
    627                 if (changes.isEmpty()) {
    628                     mChangesList.remove(changes);
    629                 }
    630             }
    631         }
    632 
    633         cancelAll(mRemoveAnimations);
    634         cancelAll(mMoveAnimations);
    635         cancelAll(mAddAnimations);
    636         cancelAll(mChangeAnimations);
    637 
    638         dispatchAnimationsFinished();
    639     }
    640 
    641     void cancelAll(List<ViewHolder> viewHolders) {
    642         for (int i = viewHolders.size() - 1; i >= 0; i--) {
    643             viewHolders.get(i).itemView.animate().cancel();
    644         }
    645     }
    646 
    647     /**
    648      * {@inheritDoc}
    649      * <p>
    650      * If the payload list is not empty, DefaultItemAnimator returns <code>true</code>.
    651      * When this is the case:
    652      * <ul>
    653      * <li>If you override {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)}, both
    654      * ViewHolder arguments will be the same instance.
    655      * </li>
    656      * <li>
    657      * If you are not overriding {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)},
    658      * then DefaultItemAnimator will call {@link #animateMove(ViewHolder, int, int, int, int)} and
    659      * run a move animation instead.
    660      * </li>
    661      * </ul>
    662      */
    663     @Override
    664     public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder,
    665             @NonNull List<Object> payloads) {
    666         return !payloads.isEmpty() || super.canReuseUpdatedViewHolder(viewHolder, payloads);
    667     }
    668 }
    669