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