1 /* 2 * Copyright (C) 2015 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 com.android.car.dialer; 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.support.v7.widget.SimpleItemAnimator; 23 import android.view.View; 24 25 import java.util.ArrayList; 26 import java.util.List; 27 28 /** 29 * Branch from {@link android.support.v7.widget.DefaultItemAnimator} with changes on 30 * {@link #animateAddImpl}, {@link #animateAdd} and {@link #animateRemoveImpl}. 31 */ 32 public class StrequentsItemAnimator extends SimpleItemAnimator { 33 private static final boolean DEBUG = false; 34 35 private ArrayList<ViewHolder> mPendingRemovals = new ArrayList<>(); 36 private ArrayList<ViewHolder> mPendingAdditions = new ArrayList<>(); 37 private ArrayList<MoveInfo> mPendingMoves = new ArrayList<>(); 38 private ArrayList<ChangeInfo> mPendingChanges = new ArrayList<>(); 39 40 private ArrayList<ArrayList<ViewHolder>> mAdditionsList = new ArrayList<>(); 41 private ArrayList<ArrayList<MoveInfo>> mMovesList = new ArrayList<>(); 42 private ArrayList<ArrayList<ChangeInfo>> mChangesList = new ArrayList<>(); 43 44 private ArrayList<ViewHolder> mAddAnimations = new ArrayList<>(); 45 private ArrayList<ViewHolder> mMoveAnimations = new ArrayList<>(); 46 private ArrayList<ViewHolder> mRemoveAnimations = new ArrayList<>(); 47 private ArrayList<ViewHolder> mChangeAnimations = new ArrayList<>(); 48 49 private static class MoveInfo { 50 public ViewHolder holder; 51 public int fromX, fromY, toX, toY; 52 MoveInfo(ViewHolder holder, int fromX, int fromY, int toX, int toY)53 private MoveInfo(ViewHolder holder, int fromX, int fromY, int toX, int toY) { 54 this.holder = holder; 55 this.fromX = fromX; 56 this.fromY = fromY; 57 this.toX = toX; 58 this.toY = toY; 59 } 60 } 61 62 private static class ChangeInfo { 63 public ViewHolder oldHolder, newHolder; 64 public int fromX, fromY, toX, toY; ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder)65 private ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder) { 66 this.oldHolder = oldHolder; 67 this.newHolder = newHolder; 68 } 69 ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder, int fromX, int fromY, int toX, int toY)70 private ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder, 71 int fromX, int fromY, int toX, int toY) { 72 this(oldHolder, newHolder); 73 this.fromX = fromX; 74 this.fromY = fromY; 75 this.toX = toX; 76 this.toY = toY; 77 } 78 79 @Override toString()80 public String toString() { 81 return "ChangeInfo{" + 82 "oldHolder=" + oldHolder + 83 ", newHolder=" + newHolder + 84 ", fromX=" + fromX + 85 ", fromY=" + fromY + 86 ", toX=" + toX + 87 ", toY=" + toY + 88 '}'; 89 } 90 } 91 92 @Override runPendingAnimations()93 public void runPendingAnimations() { 94 boolean removalsPending = !mPendingRemovals.isEmpty(); 95 boolean movesPending = !mPendingMoves.isEmpty(); 96 boolean changesPending = !mPendingChanges.isEmpty(); 97 boolean additionsPending = !mPendingAdditions.isEmpty(); 98 if (!removalsPending && !movesPending && !additionsPending && !changesPending) { 99 // nothing to animate 100 return; 101 } 102 // First, remove stuff 103 for (ViewHolder holder : mPendingRemovals) { 104 animateRemoveImpl(holder); 105 } 106 mPendingRemovals.clear(); 107 // Next, move stuff 108 if (movesPending) { 109 final ArrayList<MoveInfo> moves = new ArrayList<MoveInfo>(); 110 moves.addAll(mPendingMoves); 111 mMovesList.add(moves); 112 mPendingMoves.clear(); 113 Runnable mover = new Runnable() { 114 @Override 115 public void run() { 116 for (MoveInfo moveInfo : moves) { 117 animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY, 118 moveInfo.toX, moveInfo.toY); 119 } 120 moves.clear(); 121 mMovesList.remove(moves); 122 } 123 }; 124 if (removalsPending) { 125 View view = moves.get(0).holder.itemView; 126 ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration()); 127 } else { 128 mover.run(); 129 } 130 } 131 // Next, change stuff, to run in parallel with move animations 132 if (changesPending) { 133 final ArrayList<ChangeInfo> changes = new ArrayList<ChangeInfo>(); 134 changes.addAll(mPendingChanges); 135 mChangesList.add(changes); 136 mPendingChanges.clear(); 137 Runnable changer = new Runnable() { 138 @Override 139 public void run() { 140 for (ChangeInfo change : changes) { 141 animateChangeImpl(change); 142 } 143 changes.clear(); 144 mChangesList.remove(changes); 145 } 146 }; 147 if (removalsPending) { 148 ViewHolder holder = changes.get(0).oldHolder; 149 ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration()); 150 } else { 151 changer.run(); 152 } 153 } 154 // Next, add stuff 155 if (additionsPending) { 156 final ArrayList<ViewHolder> additions = new ArrayList<ViewHolder>(); 157 additions.addAll(mPendingAdditions); 158 mAdditionsList.add(additions); 159 mPendingAdditions.clear(); 160 Runnable adder = new Runnable() { 161 public void run() { 162 for (ViewHolder holder : additions) { 163 animateAddImpl(holder); 164 } 165 additions.clear(); 166 mAdditionsList.remove(additions); 167 } 168 }; 169 if (removalsPending || movesPending || changesPending) { 170 long removeDuration = removalsPending ? getRemoveDuration() : 0; 171 long moveDuration = movesPending ? getMoveDuration() : 0; 172 long changeDuration = changesPending ? getChangeDuration() : 0; 173 long totalDelay = removeDuration + Math.max(moveDuration, changeDuration); 174 View view = additions.get(0).itemView; 175 ViewCompat.postOnAnimationDelayed(view, adder, totalDelay); 176 } else { 177 adder.run(); 178 } 179 } 180 } 181 182 @Override animateRemove(final ViewHolder holder)183 public boolean animateRemove(final ViewHolder holder) { 184 endAnimation(holder); 185 mPendingRemovals.add(holder); 186 return true; 187 } 188 animateRemoveImpl(final ViewHolder holder)189 private void animateRemoveImpl(final ViewHolder holder) { 190 // Animate on the content if it's CallLogViewHolder. 191 final View view = holder instanceof CallLogViewHolder ? 192 ((CallLogViewHolder) holder).container : holder.itemView; 193 final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view); 194 mRemoveAnimations.add(holder); 195 animation.setDuration(getRemoveDuration()) 196 .alpha(0).setListener(new VpaListenerAdapter() { 197 @Override 198 public void onAnimationStart(View view) { 199 dispatchRemoveStarting(holder); 200 } 201 202 @Override 203 public void onAnimationEnd(View view) { 204 animation.setListener(null); 205 ViewCompat.setAlpha(view, 1); 206 dispatchRemoveFinished(holder); 207 mRemoveAnimations.remove(holder); 208 dispatchFinishedWhenDone(); 209 } 210 }).start(); 211 } 212 213 @Override animateAdd(final ViewHolder holder)214 public boolean animateAdd(final ViewHolder holder) { 215 endAnimation(holder); 216 // for CallLogViewHolder, instead of fade out the whole card, fade out only the content. 217 if (holder instanceof CallLogViewHolder) { 218 ViewCompat.setAlpha(((CallLogViewHolder) holder).container, 0); 219 } else { 220 ViewCompat.setAlpha(holder.itemView, 0); 221 } 222 mPendingAdditions.add(holder); 223 return true; 224 } 225 animateAddImpl(final ViewHolder holder)226 private void animateAddImpl(final ViewHolder holder) { 227 // Animate on the content if it's CallLogViewHolder. 228 final View view = holder instanceof CallLogViewHolder ? 229 ((CallLogViewHolder) holder).container : holder.itemView; 230 final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view); 231 mAddAnimations.add(holder); 232 animation.alpha(1).setDuration(getAddDuration()). 233 setListener(new VpaListenerAdapter() { 234 @Override 235 public void onAnimationStart(View view) { 236 dispatchAddStarting(holder); 237 } 238 @Override 239 public void onAnimationCancel(View view) { 240 ViewCompat.setAlpha(view, 1); 241 } 242 243 @Override 244 public void onAnimationEnd(View view) { 245 animation.setListener(null); 246 dispatchAddFinished(holder); 247 mAddAnimations.remove(holder); 248 dispatchFinishedWhenDone(); 249 } 250 }).start(); 251 } 252 253 @Override animateMove(final ViewHolder holder, int fromX, int fromY, int toX, int toY)254 public boolean animateMove(final ViewHolder holder, int fromX, int fromY, 255 int toX, int toY) { 256 final View view = holder.itemView; 257 fromX += ViewCompat.getTranslationX(holder.itemView); 258 fromY += ViewCompat.getTranslationY(holder.itemView); 259 endAnimation(holder); 260 int deltaX = toX - fromX; 261 int deltaY = toY - fromY; 262 if (deltaX == 0 && deltaY == 0) { 263 dispatchMoveFinished(holder); 264 return false; 265 } 266 if (deltaX != 0) { 267 ViewCompat.setTranslationX(view, -deltaX); 268 } 269 if (deltaY != 0) { 270 ViewCompat.setTranslationY(view, -deltaY); 271 } 272 mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY)); 273 return true; 274 } 275 animateMoveImpl(final ViewHolder holder, int fromX, int fromY, int toX, int toY)276 private void animateMoveImpl(final ViewHolder holder, int fromX, int fromY, int toX, int toY) { 277 final View view = holder.itemView; 278 final int deltaX = toX - fromX; 279 final int deltaY = toY - fromY; 280 if (deltaX != 0) { 281 ViewCompat.animate(view).translationX(0); 282 } 283 if (deltaY != 0) { 284 ViewCompat.animate(view).translationY(0); 285 } 286 // TODO: make EndActions end listeners instead, since end actions aren't called when 287 // vpas are canceled (and can't end them. why?) 288 // need listener functionality in VPACompat for this. Ick. 289 final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view); 290 mMoveAnimations.add(holder); 291 animation.setDuration(getMoveDuration()).setListener(new VpaListenerAdapter() { 292 @Override 293 public void onAnimationStart(View view) { 294 dispatchMoveStarting(holder); 295 } 296 @Override 297 public void onAnimationCancel(View view) { 298 if (deltaX != 0) { 299 ViewCompat.setTranslationX(view, 0); 300 } 301 if (deltaY != 0) { 302 ViewCompat.setTranslationY(view, 0); 303 } 304 } 305 @Override 306 public void onAnimationEnd(View view) { 307 animation.setListener(null); 308 dispatchMoveFinished(holder); 309 mMoveAnimations.remove(holder); 310 dispatchFinishedWhenDone(); 311 } 312 }).start(); 313 } 314 315 @Override animateChange(ViewHolder oldHolder, ViewHolder newHolder, int fromX, int fromY, int toX, int toY)316 public boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder, 317 int fromX, int fromY, int toX, int toY) { 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 endAnimation(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 && newHolder.itemView != null) { 329 // carry over translation values 330 endAnimation(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 if (mRemoveAnimations.remove(item) && DEBUG) { 488 throw new IllegalStateException("after animation is cancelled, item should not be in " 489 + "mRemoveAnimations list"); 490 } 491 492 if (mAddAnimations.remove(item) && DEBUG) { 493 throw new IllegalStateException("after animation is cancelled, item should not be in " 494 + "mAddAnimations list"); 495 } 496 497 if (mChangeAnimations.remove(item) && DEBUG) { 498 throw new IllegalStateException("after animation is cancelled, item should not be in " 499 + "mChangeAnimations list"); 500 } 501 502 if (mMoveAnimations.remove(item) && DEBUG) { 503 throw new IllegalStateException("after animation is cancelled, item should not be in " 504 + "mMoveAnimations list"); 505 } 506 dispatchFinishedWhenDone(); 507 } 508 509 @Override isRunning()510 public boolean isRunning() { 511 return (!mPendingAdditions.isEmpty() || 512 !mPendingChanges.isEmpty() || 513 !mPendingMoves.isEmpty() || 514 !mPendingRemovals.isEmpty() || 515 !mMoveAnimations.isEmpty() || 516 !mRemoveAnimations.isEmpty() || 517 !mAddAnimations.isEmpty() || 518 !mChangeAnimations.isEmpty() || 519 !mMovesList.isEmpty() || 520 !mAdditionsList.isEmpty() || 521 !mChangesList.isEmpty()); 522 } 523 524 /** 525 * Check the state of currently pending and running animations. If there are none 526 * pending/running, call {@link #dispatchAnimationsFinished()} to notify any 527 * listeners. 528 */ dispatchFinishedWhenDone()529 private void dispatchFinishedWhenDone() { 530 if (!isRunning()) { 531 dispatchAnimationsFinished(); 532 } 533 } 534 535 @Override endAnimations()536 public void endAnimations() { 537 int count = mPendingMoves.size(); 538 for (int i = count - 1; i >= 0; i--) { 539 MoveInfo item = mPendingMoves.get(i); 540 View view = item.holder.itemView; 541 ViewCompat.setTranslationY(view, 0); 542 ViewCompat.setTranslationX(view, 0); 543 dispatchMoveFinished(item.holder); 544 mPendingMoves.remove(i); 545 } 546 count = mPendingRemovals.size(); 547 for (int i = count - 1; i >= 0; i--) { 548 ViewHolder item = mPendingRemovals.get(i); 549 dispatchRemoveFinished(item); 550 mPendingRemovals.remove(i); 551 } 552 count = mPendingAdditions.size(); 553 for (int i = count - 1; i >= 0; i--) { 554 ViewHolder item = mPendingAdditions.get(i); 555 View view = item.itemView; 556 ViewCompat.setAlpha(view, 1); 557 dispatchAddFinished(item); 558 mPendingAdditions.remove(i); 559 } 560 count = mPendingChanges.size(); 561 for (int i = count - 1; i >= 0; i--) { 562 endChangeAnimationIfNecessary(mPendingChanges.get(i)); 563 } 564 mPendingChanges.clear(); 565 if (!isRunning()) { 566 return; 567 } 568 569 int listCount = mMovesList.size(); 570 for (int i = listCount - 1; i >= 0; i--) { 571 ArrayList<MoveInfo> moves = mMovesList.get(i); 572 count = moves.size(); 573 for (int j = count - 1; j >= 0; j--) { 574 MoveInfo moveInfo = moves.get(j); 575 ViewHolder item = moveInfo.holder; 576 View view = item.itemView; 577 ViewCompat.setTranslationY(view, 0); 578 ViewCompat.setTranslationX(view, 0); 579 dispatchMoveFinished(moveInfo.holder); 580 moves.remove(j); 581 if (moves.isEmpty()) { 582 mMovesList.remove(moves); 583 } 584 } 585 } 586 listCount = mAdditionsList.size(); 587 for (int i = listCount - 1; i >= 0; i--) { 588 ArrayList<ViewHolder> additions = mAdditionsList.get(i); 589 count = additions.size(); 590 for (int j = count - 1; j >= 0; j--) { 591 ViewHolder item = additions.get(j); 592 View view = item.itemView; 593 ViewCompat.setAlpha(view, 1); 594 dispatchAddFinished(item); 595 additions.remove(j); 596 if (additions.isEmpty()) { 597 mAdditionsList.remove(additions); 598 } 599 } 600 } 601 listCount = mChangesList.size(); 602 for (int i = listCount - 1; i >= 0; i--) { 603 ArrayList<ChangeInfo> changes = mChangesList.get(i); 604 count = changes.size(); 605 for (int j = count - 1; j >= 0; j--) { 606 endChangeAnimationIfNecessary(changes.get(j)); 607 if (changes.isEmpty()) { 608 mChangesList.remove(changes); 609 } 610 } 611 } 612 613 cancelAll(mRemoveAnimations); 614 cancelAll(mMoveAnimations); 615 cancelAll(mAddAnimations); 616 cancelAll(mChangeAnimations); 617 618 dispatchAnimationsFinished(); 619 } 620 cancelAll(List<ViewHolder> viewHolders)621 void cancelAll(List<ViewHolder> viewHolders) { 622 for (int i = viewHolders.size() - 1; i >= 0; i--) { 623 ViewCompat.animate(viewHolders.get(i).itemView).cancel(); 624 } 625 } 626 627 private static class VpaListenerAdapter implements ViewPropertyAnimatorListener { 628 @Override onAnimationStart(View view)629 public void onAnimationStart(View view) {} 630 631 @Override onAnimationEnd(View view)632 public void onAnimationEnd(View view) {} 633 634 @Override onAnimationCancel(View view)635 public void onAnimationCancel(View view) {} 636 }; 637 } 638