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