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