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