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 17 package android.support.v7.widget; 18 19 import android.support.v4.util.Pools; 20 import android.util.Log; 21 22 import java.util.ArrayList; 23 import java.util.Collections; 24 import java.util.List; 25 26 import static android.support.v7.widget.RecyclerView.*; 27 28 /** 29 * Helper class that can enqueue and process adapter update operations. 30 * <p> 31 * To support animations, RecyclerView presents an older version the Adapter to best represent 32 * previous state of the layout. Sometimes, this is not trivial when items are removed that were 33 * not laid out, in which case, RecyclerView has no way of providing that item's view for 34 * animations. 35 * <p> 36 * AdapterHelper creates an UpdateOp for each adapter data change then pre-processes them. During 37 * pre processing, AdapterHelper finds out which UpdateOps can be deferred to second layout pass 38 * and which cannot. For the UpdateOps that cannot be deferred, AdapterHelper will change them 39 * according to previously deferred operation and dispatch them before the first layout pass. It 40 * also takes care of updating deferred UpdateOps since order of operations is changed by this 41 * process. 42 * <p> 43 * Although operations may be forwarded to LayoutManager in different orders, resulting data set 44 * is guaranteed to be the consistent. 45 */ 46 class AdapterHelper implements OpReorderer.Callback { 47 48 final static int POSITION_TYPE_INVISIBLE = 0; 49 50 final static int POSITION_TYPE_NEW_OR_LAID_OUT = 1; 51 52 private static final boolean DEBUG = false; 53 54 private static final String TAG = "AHT"; 55 56 private Pools.Pool<UpdateOp> mUpdateOpPool = new Pools.SimplePool<UpdateOp>(UpdateOp.POOL_SIZE); 57 58 final ArrayList<UpdateOp> mPendingUpdates = new ArrayList<UpdateOp>(); 59 60 final ArrayList<UpdateOp> mPostponedList = new ArrayList<UpdateOp>(); 61 62 final Callback mCallback; 63 64 Runnable mOnItemProcessedCallback; 65 66 final boolean mDisableRecycler; 67 68 final OpReorderer mOpReorderer; 69 AdapterHelper(Callback callback)70 AdapterHelper(Callback callback) { 71 this(callback, false); 72 } 73 AdapterHelper(Callback callback, boolean disableRecycler)74 AdapterHelper(Callback callback, boolean disableRecycler) { 75 mCallback = callback; 76 mDisableRecycler = disableRecycler; 77 mOpReorderer = new OpReorderer(this); 78 } 79 addUpdateOp(UpdateOp... ops)80 AdapterHelper addUpdateOp(UpdateOp... ops) { 81 Collections.addAll(mPendingUpdates, ops); 82 return this; 83 } 84 reset()85 void reset() { 86 recycleUpdateOpsAndClearList(mPendingUpdates); 87 recycleUpdateOpsAndClearList(mPostponedList); 88 } 89 preProcess()90 void preProcess() { 91 mOpReorderer.reorderOps(mPendingUpdates); 92 final int count = mPendingUpdates.size(); 93 for (int i = 0; i < count; i++) { 94 UpdateOp op = mPendingUpdates.get(i); 95 switch (op.cmd) { 96 case UpdateOp.ADD: 97 applyAdd(op); 98 break; 99 case UpdateOp.REMOVE: 100 applyRemove(op); 101 break; 102 case UpdateOp.UPDATE: 103 applyUpdate(op); 104 break; 105 case UpdateOp.MOVE: 106 applyMove(op); 107 break; 108 } 109 if (mOnItemProcessedCallback != null) { 110 mOnItemProcessedCallback.run(); 111 } 112 } 113 mPendingUpdates.clear(); 114 } 115 consumePostponedUpdates()116 void consumePostponedUpdates() { 117 final int count = mPostponedList.size(); 118 for (int i = 0; i < count; i++) { 119 mCallback.onDispatchSecondPass(mPostponedList.get(i)); 120 } 121 recycleUpdateOpsAndClearList(mPostponedList); 122 } 123 applyMove(UpdateOp op)124 private void applyMove(UpdateOp op) { 125 // MOVE ops are pre-processed so at this point, we know that item is still in the adapter. 126 // otherwise, it would be converted into a REMOVE operation 127 postponeAndUpdateViewHolders(op); 128 } 129 applyRemove(UpdateOp op)130 private void applyRemove(UpdateOp op) { 131 int tmpStart = op.positionStart; 132 int tmpCount = 0; 133 int tmpEnd = op.positionStart + op.itemCount; 134 int type = -1; 135 for (int position = op.positionStart; position < tmpEnd; position++) { 136 boolean typeChanged = false; 137 ViewHolder vh = mCallback.findViewHolder(position); 138 if (vh != null || canFindInPreLayout(position)) { 139 // If a ViewHolder exists or this is a newly added item, we can defer this update 140 // to post layout stage. 141 // * For existing ViewHolders, we'll fake its existence in the pre-layout phase. 142 // * For items that are added and removed in the same process cycle, they won't 143 // have any effect in pre-layout since their add ops are already deferred to 144 // post-layout pass. 145 if (type == POSITION_TYPE_INVISIBLE) { 146 // Looks like we have other updates that we cannot merge with this one. 147 // Create an UpdateOp and dispatch it to LayoutManager. 148 UpdateOp newOp = obtainUpdateOp(UpdateOp.REMOVE, tmpStart, tmpCount, null); 149 dispatchAndUpdateViewHolders(newOp); 150 typeChanged = true; 151 } 152 type = POSITION_TYPE_NEW_OR_LAID_OUT; 153 } else { 154 // This update cannot be recovered because we don't have a ViewHolder representing 155 // this position. Instead, post it to LayoutManager immediately 156 if (type == POSITION_TYPE_NEW_OR_LAID_OUT) { 157 // Looks like we have other updates that we cannot merge with this one. 158 // Create UpdateOp op and dispatch it to LayoutManager. 159 UpdateOp newOp = obtainUpdateOp(UpdateOp.REMOVE, tmpStart, tmpCount, null); 160 postponeAndUpdateViewHolders(newOp); 161 typeChanged = true; 162 } 163 type = POSITION_TYPE_INVISIBLE; 164 } 165 if (typeChanged) { 166 position -= tmpCount; // also equal to tmpStart 167 tmpEnd -= tmpCount; 168 tmpCount = 1; 169 } else { 170 tmpCount++; 171 } 172 } 173 if (tmpCount != op.itemCount) { // all 1 effect 174 recycleUpdateOp(op); 175 op = obtainUpdateOp(UpdateOp.REMOVE, tmpStart, tmpCount, null); 176 } 177 if (type == POSITION_TYPE_INVISIBLE) { 178 dispatchAndUpdateViewHolders(op); 179 } else { 180 postponeAndUpdateViewHolders(op); 181 } 182 } 183 applyUpdate(UpdateOp op)184 private void applyUpdate(UpdateOp op) { 185 int tmpStart = op.positionStart; 186 int tmpCount = 0; 187 int tmpEnd = op.positionStart + op.itemCount; 188 int type = -1; 189 for (int position = op.positionStart; position < tmpEnd; position++) { 190 ViewHolder vh = mCallback.findViewHolder(position); 191 if (vh != null || canFindInPreLayout(position)) { // deferred 192 if (type == POSITION_TYPE_INVISIBLE) { 193 UpdateOp newOp = obtainUpdateOp(UpdateOp.UPDATE, tmpStart, tmpCount, 194 op.payload); 195 dispatchAndUpdateViewHolders(newOp); 196 tmpCount = 0; 197 tmpStart = position; 198 } 199 type = POSITION_TYPE_NEW_OR_LAID_OUT; 200 } else { // applied 201 if (type == POSITION_TYPE_NEW_OR_LAID_OUT) { 202 UpdateOp newOp = obtainUpdateOp(UpdateOp.UPDATE, tmpStart, tmpCount, 203 op.payload); 204 postponeAndUpdateViewHolders(newOp); 205 tmpCount = 0; 206 tmpStart = position; 207 } 208 type = POSITION_TYPE_INVISIBLE; 209 } 210 tmpCount++; 211 } 212 if (tmpCount != op.itemCount) { // all 1 effect 213 Object payload = op.payload; 214 recycleUpdateOp(op); 215 op = obtainUpdateOp(UpdateOp.UPDATE, tmpStart, tmpCount, payload); 216 } 217 if (type == POSITION_TYPE_INVISIBLE) { 218 dispatchAndUpdateViewHolders(op); 219 } else { 220 postponeAndUpdateViewHolders(op); 221 } 222 } 223 dispatchAndUpdateViewHolders(UpdateOp op)224 private void dispatchAndUpdateViewHolders(UpdateOp op) { 225 // tricky part. 226 // traverse all postpones and revert their changes on this op if necessary, apply updated 227 // dispatch to them since now they are after this op. 228 if (op.cmd == UpdateOp.ADD || op.cmd == UpdateOp.MOVE) { 229 throw new IllegalArgumentException("should not dispatch add or move for pre layout"); 230 } 231 if (DEBUG) { 232 Log.d(TAG, "dispatch (pre)" + op); 233 Log.d(TAG, "postponed state before:"); 234 for (UpdateOp updateOp : mPostponedList) { 235 Log.d(TAG, updateOp.toString()); 236 } 237 Log.d(TAG, "----"); 238 } 239 240 // handle each pos 1 by 1 to ensure continuity. If it breaks, dispatch partial 241 // TODO Since move ops are pushed to end, we should not need this anymore 242 int tmpStart = updatePositionWithPostponed(op.positionStart, op.cmd); 243 if (DEBUG) { 244 Log.d(TAG, "pos:" + op.positionStart + ",updatedPos:" + tmpStart); 245 } 246 int tmpCnt = 1; 247 int offsetPositionForPartial = op.positionStart; 248 final int positionMultiplier; 249 switch (op.cmd) { 250 case UpdateOp.UPDATE: 251 positionMultiplier = 1; 252 break; 253 case UpdateOp.REMOVE: 254 positionMultiplier = 0; 255 break; 256 default: 257 throw new IllegalArgumentException("op should be remove or update." + op); 258 } 259 for (int p = 1; p < op.itemCount; p++) { 260 final int pos = op.positionStart + (positionMultiplier * p); 261 int updatedPos = updatePositionWithPostponed(pos, op.cmd); 262 if (DEBUG) { 263 Log.d(TAG, "pos:" + pos + ",updatedPos:" + updatedPos); 264 } 265 boolean continuous = false; 266 switch (op.cmd) { 267 case UpdateOp.UPDATE: 268 continuous = updatedPos == tmpStart + 1; 269 break; 270 case UpdateOp.REMOVE: 271 continuous = updatedPos == tmpStart; 272 break; 273 } 274 if (continuous) { 275 tmpCnt++; 276 } else { 277 // need to dispatch this separately 278 UpdateOp tmp = obtainUpdateOp(op.cmd, tmpStart, tmpCnt, op.payload); 279 if (DEBUG) { 280 Log.d(TAG, "need to dispatch separately " + tmp); 281 } 282 dispatchFirstPassAndUpdateViewHolders(tmp, offsetPositionForPartial); 283 recycleUpdateOp(tmp); 284 if (op.cmd == UpdateOp.UPDATE) { 285 offsetPositionForPartial += tmpCnt; 286 } 287 tmpStart = updatedPos;// need to remove previously dispatched 288 tmpCnt = 1; 289 } 290 } 291 Object payload = op.payload; 292 recycleUpdateOp(op); 293 if (tmpCnt > 0) { 294 UpdateOp tmp = obtainUpdateOp(op.cmd, tmpStart, tmpCnt, payload); 295 if (DEBUG) { 296 Log.d(TAG, "dispatching:" + tmp); 297 } 298 dispatchFirstPassAndUpdateViewHolders(tmp, offsetPositionForPartial); 299 recycleUpdateOp(tmp); 300 } 301 if (DEBUG) { 302 Log.d(TAG, "post dispatch"); 303 Log.d(TAG, "postponed state after:"); 304 for (UpdateOp updateOp : mPostponedList) { 305 Log.d(TAG, updateOp.toString()); 306 } 307 Log.d(TAG, "----"); 308 } 309 } 310 dispatchFirstPassAndUpdateViewHolders(UpdateOp op, int offsetStart)311 void dispatchFirstPassAndUpdateViewHolders(UpdateOp op, int offsetStart) { 312 mCallback.onDispatchFirstPass(op); 313 switch (op.cmd) { 314 case UpdateOp.REMOVE: 315 mCallback.offsetPositionsForRemovingInvisible(offsetStart, op.itemCount); 316 break; 317 case UpdateOp.UPDATE: 318 mCallback.markViewHoldersUpdated(offsetStart, op.itemCount, op.payload); 319 break; 320 default: 321 throw new IllegalArgumentException("only remove and update ops can be dispatched" 322 + " in first pass"); 323 } 324 } 325 updatePositionWithPostponed(int pos, int cmd)326 private int updatePositionWithPostponed(int pos, int cmd) { 327 final int count = mPostponedList.size(); 328 for (int i = count - 1; i >= 0; i--) { 329 UpdateOp postponed = mPostponedList.get(i); 330 if (postponed.cmd == UpdateOp.MOVE) { 331 int start, end; 332 if (postponed.positionStart < postponed.itemCount) { 333 start = postponed.positionStart; 334 end = postponed.itemCount; 335 } else { 336 start = postponed.itemCount; 337 end = postponed.positionStart; 338 } 339 if (pos >= start && pos <= end) { 340 //i'm affected 341 if (start == postponed.positionStart) { 342 if (cmd == UpdateOp.ADD) { 343 postponed.itemCount++; 344 } else if (cmd == UpdateOp.REMOVE) { 345 postponed.itemCount--; 346 } 347 // op moved to left, move it right to revert 348 pos++; 349 } else { 350 if (cmd == UpdateOp.ADD) { 351 postponed.positionStart++; 352 } else if (cmd == UpdateOp.REMOVE) { 353 postponed.positionStart--; 354 } 355 // op was moved right, move left to revert 356 pos--; 357 } 358 } else if (pos < postponed.positionStart) { 359 // postponed MV is outside the dispatched OP. if it is before, offset 360 if (cmd == UpdateOp.ADD) { 361 postponed.positionStart++; 362 postponed.itemCount++; 363 } else if (cmd == UpdateOp.REMOVE) { 364 postponed.positionStart--; 365 postponed.itemCount--; 366 } 367 } 368 } else { 369 if (postponed.positionStart <= pos) { 370 if (postponed.cmd == UpdateOp.ADD) { 371 pos -= postponed.itemCount; 372 } else if (postponed.cmd == UpdateOp.REMOVE) { 373 pos += postponed.itemCount; 374 } 375 } else { 376 if (cmd == UpdateOp.ADD) { 377 postponed.positionStart++; 378 } else if (cmd == UpdateOp.REMOVE) { 379 postponed.positionStart--; 380 } 381 } 382 } 383 if (DEBUG) { 384 Log.d(TAG, "dispath (step" + i + ")"); 385 Log.d(TAG, "postponed state:" + i + ", pos:" + pos); 386 for (UpdateOp updateOp : mPostponedList) { 387 Log.d(TAG, updateOp.toString()); 388 } 389 Log.d(TAG, "----"); 390 } 391 } 392 for (int i = mPostponedList.size() - 1; i >= 0; i--) { 393 UpdateOp op = mPostponedList.get(i); 394 if (op.cmd == UpdateOp.MOVE) { 395 if (op.itemCount == op.positionStart || op.itemCount < 0) { 396 mPostponedList.remove(i); 397 recycleUpdateOp(op); 398 } 399 } else if (op.itemCount <= 0) { 400 mPostponedList.remove(i); 401 recycleUpdateOp(op); 402 } 403 } 404 return pos; 405 } 406 canFindInPreLayout(int position)407 private boolean canFindInPreLayout(int position) { 408 final int count = mPostponedList.size(); 409 for (int i = 0; i < count; i++) { 410 UpdateOp op = mPostponedList.get(i); 411 if (op.cmd == UpdateOp.MOVE) { 412 if (findPositionOffset(op.itemCount, i + 1) == position) { 413 return true; 414 } 415 } else if (op.cmd == UpdateOp.ADD) { 416 // TODO optimize. 417 final int end = op.positionStart + op.itemCount; 418 for (int pos = op.positionStart; pos < end; pos++) { 419 if (findPositionOffset(pos, i + 1) == position) { 420 return true; 421 } 422 } 423 } 424 } 425 return false; 426 } 427 applyAdd(UpdateOp op)428 private void applyAdd(UpdateOp op) { 429 postponeAndUpdateViewHolders(op); 430 } 431 postponeAndUpdateViewHolders(UpdateOp op)432 private void postponeAndUpdateViewHolders(UpdateOp op) { 433 if (DEBUG) { 434 Log.d(TAG, "postponing " + op); 435 } 436 mPostponedList.add(op); 437 switch (op.cmd) { 438 case UpdateOp.ADD: 439 mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount); 440 break; 441 case UpdateOp.MOVE: 442 mCallback.offsetPositionsForMove(op.positionStart, op.itemCount); 443 break; 444 case UpdateOp.REMOVE: 445 mCallback.offsetPositionsForRemovingLaidOutOrNewView(op.positionStart, 446 op.itemCount); 447 break; 448 case UpdateOp.UPDATE: 449 mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount, op.payload); 450 break; 451 default: 452 throw new IllegalArgumentException("Unknown update op type for " + op); 453 } 454 } 455 hasPendingUpdates()456 boolean hasPendingUpdates() { 457 return mPendingUpdates.size() > 0; 458 } 459 findPositionOffset(int position)460 int findPositionOffset(int position) { 461 return findPositionOffset(position, 0); 462 } 463 findPositionOffset(int position, int firstPostponedItem)464 int findPositionOffset(int position, int firstPostponedItem) { 465 int count = mPostponedList.size(); 466 for (int i = firstPostponedItem; i < count; ++i) { 467 UpdateOp op = mPostponedList.get(i); 468 if (op.cmd == UpdateOp.MOVE) { 469 if (op.positionStart == position) { 470 position = op.itemCount; 471 } else { 472 if (op.positionStart < position) { 473 position--; // like a remove 474 } 475 if (op.itemCount <= position) { 476 position++; // like an add 477 } 478 } 479 } else if (op.positionStart <= position) { 480 if (op.cmd == UpdateOp.REMOVE) { 481 if (position < op.positionStart + op.itemCount) { 482 return -1; 483 } 484 position -= op.itemCount; 485 } else if (op.cmd == UpdateOp.ADD) { 486 position += op.itemCount; 487 } 488 } 489 } 490 return position; 491 } 492 493 /** 494 * @return True if updates should be processed. 495 */ onItemRangeChanged(int positionStart, int itemCount, Object payload)496 boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) { 497 mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount, payload)); 498 return mPendingUpdates.size() == 1; 499 } 500 501 /** 502 * @return True if updates should be processed. 503 */ onItemRangeInserted(int positionStart, int itemCount)504 boolean onItemRangeInserted(int positionStart, int itemCount) { 505 mPendingUpdates.add(obtainUpdateOp(UpdateOp.ADD, positionStart, itemCount, null)); 506 return mPendingUpdates.size() == 1; 507 } 508 509 /** 510 * @return True if updates should be processed. 511 */ onItemRangeRemoved(int positionStart, int itemCount)512 boolean onItemRangeRemoved(int positionStart, int itemCount) { 513 mPendingUpdates.add(obtainUpdateOp(UpdateOp.REMOVE, positionStart, itemCount, null)); 514 return mPendingUpdates.size() == 1; 515 } 516 517 /** 518 * @return True if updates should be processed. 519 */ onItemRangeMoved(int from, int to, int itemCount)520 boolean onItemRangeMoved(int from, int to, int itemCount) { 521 if (from == to) { 522 return false;//no-op 523 } 524 if (itemCount != 1) { 525 throw new IllegalArgumentException("Moving more than 1 item is not supported yet"); 526 } 527 mPendingUpdates.add(obtainUpdateOp(UpdateOp.MOVE, from, to, null)); 528 return mPendingUpdates.size() == 1; 529 } 530 531 /** 532 * Skips pre-processing and applies all updates in one pass. 533 */ consumeUpdatesInOnePass()534 void consumeUpdatesInOnePass() { 535 // we still consume postponed updates (if there is) in case there was a pre-process call 536 // w/o a matching consumePostponedUpdates. 537 consumePostponedUpdates(); 538 final int count = mPendingUpdates.size(); 539 for (int i = 0; i < count; i++) { 540 UpdateOp op = mPendingUpdates.get(i); 541 switch (op.cmd) { 542 case UpdateOp.ADD: 543 mCallback.onDispatchSecondPass(op); 544 mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount); 545 break; 546 case UpdateOp.REMOVE: 547 mCallback.onDispatchSecondPass(op); 548 mCallback.offsetPositionsForRemovingInvisible(op.positionStart, op.itemCount); 549 break; 550 case UpdateOp.UPDATE: 551 mCallback.onDispatchSecondPass(op); 552 mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount, op.payload); 553 break; 554 case UpdateOp.MOVE: 555 mCallback.onDispatchSecondPass(op); 556 mCallback.offsetPositionsForMove(op.positionStart, op.itemCount); 557 break; 558 } 559 if (mOnItemProcessedCallback != null) { 560 mOnItemProcessedCallback.run(); 561 } 562 } 563 recycleUpdateOpsAndClearList(mPendingUpdates); 564 } 565 applyPendingUpdatesToPosition(int position)566 public int applyPendingUpdatesToPosition(int position) { 567 final int size = mPendingUpdates.size(); 568 for (int i = 0; i < size; i ++) { 569 UpdateOp op = mPendingUpdates.get(i); 570 switch (op.cmd) { 571 case UpdateOp.ADD: 572 if (op.positionStart <= position) { 573 position += op.itemCount; 574 } 575 break; 576 case UpdateOp.REMOVE: 577 if (op.positionStart <= position) { 578 final int end = op.positionStart + op.itemCount; 579 if (end > position) { 580 return RecyclerView.NO_POSITION; 581 } 582 position -= op.itemCount; 583 } 584 break; 585 case UpdateOp.MOVE: 586 if (op.positionStart == position) { 587 position = op.itemCount;//position end 588 } else { 589 if (op.positionStart < position) { 590 position -= 1; 591 } 592 if (op.itemCount <= position) { 593 position += 1; 594 } 595 } 596 break; 597 } 598 } 599 return position; 600 } 601 602 /** 603 * Queued operation to happen when child views are updated. 604 */ 605 static class UpdateOp { 606 607 static final int ADD = 0; 608 609 static final int REMOVE = 1; 610 611 static final int UPDATE = 2; 612 613 static final int MOVE = 3; 614 615 static final int POOL_SIZE = 30; 616 617 int cmd; 618 619 int positionStart; 620 621 Object payload; 622 623 // holds the target position if this is a MOVE 624 int itemCount; 625 UpdateOp(int cmd, int positionStart, int itemCount, Object payload)626 UpdateOp(int cmd, int positionStart, int itemCount, Object payload) { 627 this.cmd = cmd; 628 this.positionStart = positionStart; 629 this.itemCount = itemCount; 630 this.payload = payload; 631 } 632 cmdToString()633 String cmdToString() { 634 switch (cmd) { 635 case ADD: 636 return "add"; 637 case REMOVE: 638 return "rm"; 639 case UPDATE: 640 return "up"; 641 case MOVE: 642 return "mv"; 643 } 644 return "??"; 645 } 646 647 @Override toString()648 public String toString() { 649 return Integer.toHexString(System.identityHashCode(this)) 650 + "[" + cmdToString() + ",s:" + positionStart + "c:" + itemCount 651 +",p:"+payload + "]"; 652 } 653 654 @Override equals(Object o)655 public boolean equals(Object o) { 656 if (this == o) { 657 return true; 658 } 659 if (o == null || getClass() != o.getClass()) { 660 return false; 661 } 662 663 UpdateOp op = (UpdateOp) o; 664 665 if (cmd != op.cmd) { 666 return false; 667 } 668 if (cmd == MOVE && Math.abs(itemCount - positionStart) == 1) { 669 // reverse of this is also true 670 if (itemCount == op.positionStart && positionStart == op.itemCount) { 671 return true; 672 } 673 } 674 if (itemCount != op.itemCount) { 675 return false; 676 } 677 if (positionStart != op.positionStart) { 678 return false; 679 } 680 if (payload != null) { 681 if (!payload.equals(op.payload)) { 682 return false; 683 } 684 } else if (op.payload != null) { 685 return false; 686 } 687 688 return true; 689 } 690 691 @Override hashCode()692 public int hashCode() { 693 int result = cmd; 694 result = 31 * result + positionStart; 695 result = 31 * result + itemCount; 696 return result; 697 } 698 } 699 700 @Override obtainUpdateOp(int cmd, int positionStart, int itemCount, Object payload)701 public UpdateOp obtainUpdateOp(int cmd, int positionStart, int itemCount, Object payload) { 702 UpdateOp op = mUpdateOpPool.acquire(); 703 if (op == null) { 704 op = new UpdateOp(cmd, positionStart, itemCount, payload); 705 } else { 706 op.cmd = cmd; 707 op.positionStart = positionStart; 708 op.itemCount = itemCount; 709 op.payload = payload; 710 } 711 return op; 712 } 713 714 @Override recycleUpdateOp(UpdateOp op)715 public void recycleUpdateOp(UpdateOp op) { 716 if (!mDisableRecycler) { 717 op.payload = null; 718 mUpdateOpPool.release(op); 719 } 720 } 721 recycleUpdateOpsAndClearList(List<UpdateOp> ops)722 void recycleUpdateOpsAndClearList(List<UpdateOp> ops) { 723 final int count = ops.size(); 724 for (int i = 0; i < count; i++) { 725 recycleUpdateOp(ops.get(i)); 726 } 727 ops.clear(); 728 } 729 730 /** 731 * Contract between AdapterHelper and RecyclerView. 732 */ 733 static interface Callback { 734 findViewHolder(int position)735 ViewHolder findViewHolder(int position); 736 offsetPositionsForRemovingInvisible(int positionStart, int itemCount)737 void offsetPositionsForRemovingInvisible(int positionStart, int itemCount); 738 offsetPositionsForRemovingLaidOutOrNewView(int positionStart, int itemCount)739 void offsetPositionsForRemovingLaidOutOrNewView(int positionStart, int itemCount); 740 markViewHoldersUpdated(int positionStart, int itemCount, Object payloads)741 void markViewHoldersUpdated(int positionStart, int itemCount, Object payloads); 742 onDispatchFirstPass(UpdateOp updateOp)743 void onDispatchFirstPass(UpdateOp updateOp); 744 onDispatchSecondPass(UpdateOp updateOp)745 void onDispatchSecondPass(UpdateOp updateOp); 746 offsetPositionsForAdd(int positionStart, int itemCount)747 void offsetPositionsForAdd(int positionStart, int itemCount); 748 offsetPositionsForMove(int from, int to)749 void offsetPositionsForMove(int from, int to); 750 } 751 } 752