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