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