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