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