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 
MoveInfo(ViewHolder holder, int fromX, int fromY, int toX, int toY)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;
ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder)74         private ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder) {
75             this.oldHolder = oldHolder;
76             this.newHolder = newHolder;
77         }
78 
ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder, int fromX, int fromY, int toX, int toY)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
toString()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
runPendingAnimations()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
animateRemove(final ViewHolder holder)193     public boolean animateRemove(final ViewHolder holder) {
194         resetAnimation(holder);
195         mPendingRemovals.add(holder);
196         return true;
197     }
198 
animateRemoveImpl(final ViewHolder holder)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
animateAdd(final ViewHolder holder)222     public boolean animateAdd(final ViewHolder holder) {
223         resetAnimation(holder);
224         holder.itemView.setAlpha(0);
225         mPendingAdditions.add(holder);
226         return true;
227     }
228 
animateAddImpl(final ViewHolder holder)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
animateMove(final ViewHolder holder, int fromX, int fromY, int toX, int toY)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 
animateMoveImpl(final ViewHolder holder, int fromX, int fromY, int toX, int toY)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
animateChange(ViewHolder oldHolder, ViewHolder newHolder, int fromX, int fromY, int toX, int toY)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 
animateChangeImpl(final ChangeInfo changeInfo)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 
endChangeAnimation(List<ChangeInfo> infoList, ViewHolder item)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 
endChangeAnimationIfNecessary(ChangeInfo changeInfo)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     }
endChangeAnimationIfNecessary(ChangeInfo changeInfo, ViewHolder item)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
endAnimation(ViewHolder item)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 
resetAnimation(ViewHolder holder)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
isRunning()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      */
dispatchFinishedWhenDone()550     void dispatchFinishedWhenDone() {
551         if (!isRunning()) {
552             dispatchAnimationsFinished();
553         }
554     }
555 
556     @Override
endAnimations()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 
cancelAll(List<ViewHolder> viewHolders)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
canReuseUpdatedViewHolder(@onNull ViewHolder viewHolder, @NonNull List<Object> payloads)664     public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder,
665             @NonNull List<Object> payloads) {
666         return !payloads.isEmpty() || super.canReuseUpdatedViewHolder(viewHolder, payloads);
667     }
668 }
669