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