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); 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); 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); 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 dispatchAndUpdateViewHolders(newOp); 195 tmpCount = 0; 196 tmpStart = position; 197 } 198 type = POSITION_TYPE_NEW_OR_LAID_OUT; 199 } else { // applied 200 if (type == POSITION_TYPE_NEW_OR_LAID_OUT) { 201 UpdateOp newOp = obtainUpdateOp(UpdateOp.UPDATE, tmpStart, tmpCount); 202 postponeAndUpdateViewHolders(newOp); 203 tmpCount = 0; 204 tmpStart = position; 205 } 206 type = POSITION_TYPE_INVISIBLE; 207 } 208 tmpCount++; 209 } 210 if (tmpCount != op.itemCount) { // all 1 effect 211 recycleUpdateOp(op); 212 op = obtainUpdateOp(UpdateOp.UPDATE, tmpStart, tmpCount); 213 } 214 if (type == POSITION_TYPE_INVISIBLE) { 215 dispatchAndUpdateViewHolders(op); 216 } else { 217 postponeAndUpdateViewHolders(op); 218 } 219 } 220 dispatchAndUpdateViewHolders(UpdateOp op)221 private void dispatchAndUpdateViewHolders(UpdateOp op) { 222 // tricky part. 223 // traverse all postpones and revert their changes on this op if necessary, apply updated 224 // dispatch to them since now they are after this op. 225 if (op.cmd == UpdateOp.ADD || op.cmd == UpdateOp.MOVE) { 226 throw new IllegalArgumentException("should not dispatch add or move for pre layout"); 227 } 228 if (DEBUG) { 229 Log.d(TAG, "dispatch (pre)" + op); 230 Log.d(TAG, "postponed state before:"); 231 for (UpdateOp updateOp : mPostponedList) { 232 Log.d(TAG, updateOp.toString()); 233 } 234 Log.d(TAG, "----"); 235 } 236 237 // handle each pos 1 by 1 to ensure continuity. If it breaks, dispatch partial 238 // TODO Since move ops are pushed to end, we should not need this anymore 239 int tmpStart = updatePositionWithPostponed(op.positionStart, op.cmd); 240 if (DEBUG) { 241 Log.d(TAG, "pos:" + op.positionStart + ",updatedPos:" + tmpStart); 242 } 243 int tmpCnt = 1; 244 int offsetPositionForPartial = op.positionStart; 245 final int positionMultiplier; 246 switch (op.cmd) { 247 case UpdateOp.UPDATE: 248 positionMultiplier = 1; 249 break; 250 case UpdateOp.REMOVE: 251 positionMultiplier = 0; 252 break; 253 default: 254 throw new IllegalArgumentException("op should be remove or update." + op); 255 } 256 for (int p = 1; p < op.itemCount; p++) { 257 final int pos = op.positionStart + (positionMultiplier * p); 258 int updatedPos = updatePositionWithPostponed(pos, op.cmd); 259 if (DEBUG) { 260 Log.d(TAG, "pos:" + pos + ",updatedPos:" + updatedPos); 261 } 262 boolean continuous = false; 263 switch (op.cmd) { 264 case UpdateOp.UPDATE: 265 continuous = updatedPos == tmpStart + 1; 266 break; 267 case UpdateOp.REMOVE: 268 continuous = updatedPos == tmpStart; 269 break; 270 } 271 if (continuous) { 272 tmpCnt++; 273 } else { 274 // need to dispatch this separately 275 UpdateOp tmp = obtainUpdateOp(op.cmd, tmpStart, tmpCnt); 276 if (DEBUG) { 277 Log.d(TAG, "need to dispatch separately " + tmp); 278 } 279 dispatchFirstPassAndUpdateViewHolders(tmp, offsetPositionForPartial); 280 recycleUpdateOp(tmp); 281 if (op.cmd == UpdateOp.UPDATE) { 282 offsetPositionForPartial += tmpCnt; 283 } 284 tmpStart = updatedPos;// need to remove previously dispatched 285 tmpCnt = 1; 286 } 287 } 288 recycleUpdateOp(op); 289 if (tmpCnt > 0) { 290 UpdateOp tmp = obtainUpdateOp(op.cmd, tmpStart, tmpCnt); 291 if (DEBUG) { 292 Log.d(TAG, "dispatching:" + tmp); 293 } 294 dispatchFirstPassAndUpdateViewHolders(tmp, offsetPositionForPartial); 295 recycleUpdateOp(tmp); 296 } 297 if (DEBUG) { 298 Log.d(TAG, "post dispatch"); 299 Log.d(TAG, "postponed state after:"); 300 for (UpdateOp updateOp : mPostponedList) { 301 Log.d(TAG, updateOp.toString()); 302 } 303 Log.d(TAG, "----"); 304 } 305 } 306 dispatchFirstPassAndUpdateViewHolders(UpdateOp op, int offsetStart)307 void dispatchFirstPassAndUpdateViewHolders(UpdateOp op, int offsetStart) { 308 mCallback.onDispatchFirstPass(op); 309 switch (op.cmd) { 310 case UpdateOp.REMOVE: 311 mCallback.offsetPositionsForRemovingInvisible(offsetStart, op.itemCount); 312 break; 313 case UpdateOp.UPDATE: 314 mCallback.markViewHoldersUpdated(offsetStart, op.itemCount); 315 break; 316 default: 317 throw new IllegalArgumentException("only remove and update ops can be dispatched" 318 + " in first pass"); 319 } 320 } 321 updatePositionWithPostponed(int pos, int cmd)322 private int updatePositionWithPostponed(int pos, int cmd) { 323 final int count = mPostponedList.size(); 324 for (int i = count - 1; i >= 0; i--) { 325 UpdateOp postponed = mPostponedList.get(i); 326 if (postponed.cmd == UpdateOp.MOVE) { 327 int start, end; 328 if (postponed.positionStart < postponed.itemCount) { 329 start = postponed.positionStart; 330 end = postponed.itemCount; 331 } else { 332 start = postponed.itemCount; 333 end = postponed.positionStart; 334 } 335 if (pos >= start && pos <= end) { 336 //i'm affected 337 if (start == postponed.positionStart) { 338 if (cmd == UpdateOp.ADD) { 339 postponed.itemCount++; 340 } else if (cmd == UpdateOp.REMOVE) { 341 postponed.itemCount--; 342 } 343 // op moved to left, move it right to revert 344 pos++; 345 } else { 346 if (cmd == UpdateOp.ADD) { 347 postponed.positionStart++; 348 } else if (cmd == UpdateOp.REMOVE) { 349 postponed.positionStart--; 350 } 351 // op was moved right, move left to revert 352 pos--; 353 } 354 } else if (pos < postponed.positionStart) { 355 // postponed MV is outside the dispatched OP. if it is before, offset 356 if (cmd == UpdateOp.ADD) { 357 postponed.positionStart++; 358 postponed.itemCount++; 359 } else if (cmd == UpdateOp.REMOVE) { 360 postponed.positionStart--; 361 postponed.itemCount--; 362 } 363 } 364 } else { 365 if (postponed.positionStart <= pos) { 366 if (postponed.cmd == UpdateOp.ADD) { 367 pos -= postponed.itemCount; 368 } else if (postponed.cmd == UpdateOp.REMOVE) { 369 pos += postponed.itemCount; 370 } 371 } else { 372 if (cmd == UpdateOp.ADD) { 373 postponed.positionStart++; 374 } else if (cmd == UpdateOp.REMOVE) { 375 postponed.positionStart--; 376 } 377 } 378 } 379 if (DEBUG) { 380 Log.d(TAG, "dispath (step" + i + ")"); 381 Log.d(TAG, "postponed state:" + i + ", pos:" + pos); 382 for (UpdateOp updateOp : mPostponedList) { 383 Log.d(TAG, updateOp.toString()); 384 } 385 Log.d(TAG, "----"); 386 } 387 } 388 for (int i = mPostponedList.size() - 1; i >= 0; i--) { 389 UpdateOp op = mPostponedList.get(i); 390 if (op.cmd == UpdateOp.MOVE) { 391 if (op.itemCount == op.positionStart || op.itemCount < 0) { 392 mPostponedList.remove(i); 393 recycleUpdateOp(op); 394 } 395 } else if (op.itemCount <= 0) { 396 mPostponedList.remove(i); 397 recycleUpdateOp(op); 398 } 399 } 400 return pos; 401 } 402 canFindInPreLayout(int position)403 private boolean canFindInPreLayout(int position) { 404 final int count = mPostponedList.size(); 405 for (int i = 0; i < count; i++) { 406 UpdateOp op = mPostponedList.get(i); 407 if (op.cmd == UpdateOp.MOVE) { 408 if (findPositionOffset(op.itemCount, i + 1) == position) { 409 return true; 410 } 411 } else if (op.cmd == UpdateOp.ADD) { 412 // TODO optimize. 413 final int end = op.positionStart + op.itemCount; 414 for (int pos = op.positionStart; pos < end; pos++) { 415 if (findPositionOffset(pos, i + 1) == position) { 416 return true; 417 } 418 } 419 } 420 } 421 return false; 422 } 423 applyAdd(UpdateOp op)424 private void applyAdd(UpdateOp op) { 425 postponeAndUpdateViewHolders(op); 426 } 427 postponeAndUpdateViewHolders(UpdateOp op)428 private void postponeAndUpdateViewHolders(UpdateOp op) { 429 if (DEBUG) { 430 Log.d(TAG, "postponing " + op); 431 } 432 mPostponedList.add(op); 433 switch (op.cmd) { 434 case UpdateOp.ADD: 435 mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount); 436 break; 437 case UpdateOp.MOVE: 438 mCallback.offsetPositionsForMove(op.positionStart, op.itemCount); 439 break; 440 case UpdateOp.REMOVE: 441 mCallback.offsetPositionsForRemovingLaidOutOrNewView(op.positionStart, 442 op.itemCount); 443 break; 444 case UpdateOp.UPDATE: 445 mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount); 446 break; 447 default: 448 throw new IllegalArgumentException("Unknown update op type for " + op); 449 } 450 } 451 hasPendingUpdates()452 boolean hasPendingUpdates() { 453 return mPendingUpdates.size() > 0; 454 } 455 findPositionOffset(int position)456 int findPositionOffset(int position) { 457 return findPositionOffset(position, 0); 458 } 459 findPositionOffset(int position, int firstPostponedItem)460 int findPositionOffset(int position, int firstPostponedItem) { 461 int count = mPostponedList.size(); 462 for (int i = firstPostponedItem; i < count; ++i) { 463 UpdateOp op = mPostponedList.get(i); 464 if (op.cmd == UpdateOp.MOVE) { 465 if (op.positionStart == position) { 466 position = op.itemCount; 467 } else { 468 if (op.positionStart < position) { 469 position--; // like a remove 470 } 471 if (op.itemCount <= position) { 472 position++; // like an add 473 } 474 } 475 } else if (op.positionStart <= position) { 476 if (op.cmd == UpdateOp.REMOVE) { 477 if (position < op.positionStart + op.itemCount) { 478 return -1; 479 } 480 position -= op.itemCount; 481 } else if (op.cmd == UpdateOp.ADD) { 482 position += op.itemCount; 483 } 484 } 485 } 486 return position; 487 } 488 489 /** 490 * @return True if updates should be processed. 491 */ onItemRangeChanged(int positionStart, int itemCount)492 boolean onItemRangeChanged(int positionStart, int itemCount) { 493 mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount)); 494 return mPendingUpdates.size() == 1; 495 } 496 497 /** 498 * @return True if updates should be processed. 499 */ onItemRangeInserted(int positionStart, int itemCount)500 boolean onItemRangeInserted(int positionStart, int itemCount) { 501 mPendingUpdates.add(obtainUpdateOp(UpdateOp.ADD, positionStart, itemCount)); 502 return mPendingUpdates.size() == 1; 503 } 504 505 /** 506 * @return True if updates should be processed. 507 */ onItemRangeRemoved(int positionStart, int itemCount)508 boolean onItemRangeRemoved(int positionStart, int itemCount) { 509 mPendingUpdates.add(obtainUpdateOp(UpdateOp.REMOVE, positionStart, itemCount)); 510 return mPendingUpdates.size() == 1; 511 } 512 513 /** 514 * @return True if updates should be processed. 515 */ onItemRangeMoved(int from, int to, int itemCount)516 boolean onItemRangeMoved(int from, int to, int itemCount) { 517 if (from == to) { 518 return false;//no-op 519 } 520 if (itemCount != 1) { 521 throw new IllegalArgumentException("Moving more than 1 item is not supported yet"); 522 } 523 mPendingUpdates.add(obtainUpdateOp(UpdateOp.MOVE, from, to)); 524 return mPendingUpdates.size() == 1; 525 } 526 527 /** 528 * Skips pre-processing and applies all updates in one pass. 529 */ consumeUpdatesInOnePass()530 void consumeUpdatesInOnePass() { 531 // we still consume postponed updates (if there is) in case there was a pre-process call 532 // w/o a matching consumePostponedUpdates. 533 consumePostponedUpdates(); 534 final int count = mPendingUpdates.size(); 535 for (int i = 0; i < count; i++) { 536 UpdateOp op = mPendingUpdates.get(i); 537 switch (op.cmd) { 538 case UpdateOp.ADD: 539 mCallback.onDispatchSecondPass(op); 540 mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount); 541 break; 542 case UpdateOp.REMOVE: 543 mCallback.onDispatchSecondPass(op); 544 mCallback.offsetPositionsForRemovingInvisible(op.positionStart, op.itemCount); 545 break; 546 case UpdateOp.UPDATE: 547 mCallback.onDispatchSecondPass(op); 548 mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount); 549 break; 550 case UpdateOp.MOVE: 551 mCallback.onDispatchSecondPass(op); 552 mCallback.offsetPositionsForMove(op.positionStart, op.itemCount); 553 break; 554 } 555 if (mOnItemProcessedCallback != null) { 556 mOnItemProcessedCallback.run(); 557 } 558 } 559 recycleUpdateOpsAndClearList(mPendingUpdates); 560 } 561 applyPendingUpdatesToPosition(int position)562 public int applyPendingUpdatesToPosition(int position) { 563 final int size = mPendingUpdates.size(); 564 for (int i = 0; i < size; i ++) { 565 UpdateOp op = mPendingUpdates.get(i); 566 switch (op.cmd) { 567 case UpdateOp.ADD: 568 if (op.positionStart <= position) { 569 position += op.itemCount; 570 } 571 break; 572 case UpdateOp.REMOVE: 573 if (op.positionStart <= position) { 574 final int end = op.positionStart + op.itemCount; 575 if (end > position) { 576 return RecyclerView.NO_POSITION; 577 } 578 position -= op.itemCount; 579 } 580 break; 581 case UpdateOp.MOVE: 582 if (op.positionStart == position) { 583 position = op.itemCount;//position end 584 } else { 585 if (op.positionStart < position) { 586 position -= 1; 587 } 588 if (op.itemCount <= position) { 589 position += 1; 590 } 591 } 592 break; 593 } 594 } 595 return position; 596 } 597 598 /** 599 * Queued operation to happen when child views are updated. 600 */ 601 static class UpdateOp { 602 603 static final int ADD = 0; 604 605 static final int REMOVE = 1; 606 607 static final int UPDATE = 2; 608 609 static final int MOVE = 3; 610 611 static final int POOL_SIZE = 30; 612 613 int cmd; 614 615 int positionStart; 616 617 // holds the target position if this is a MOVE 618 int itemCount; 619 UpdateOp(int cmd, int positionStart, int itemCount)620 UpdateOp(int cmd, int positionStart, int itemCount) { 621 this.cmd = cmd; 622 this.positionStart = positionStart; 623 this.itemCount = itemCount; 624 } 625 cmdToString()626 String cmdToString() { 627 switch (cmd) { 628 case ADD: 629 return "add"; 630 case REMOVE: 631 return "rm"; 632 case UPDATE: 633 return "up"; 634 case MOVE: 635 return "mv"; 636 } 637 return "??"; 638 } 639 640 @Override toString()641 public String toString() { 642 return "[" + cmdToString() + ",s:" + positionStart + "c:" + itemCount + "]"; 643 } 644 645 @Override equals(Object o)646 public boolean equals(Object o) { 647 if (this == o) { 648 return true; 649 } 650 if (o == null || getClass() != o.getClass()) { 651 return false; 652 } 653 654 UpdateOp op = (UpdateOp) o; 655 656 if (cmd != op.cmd) { 657 return false; 658 } 659 if (cmd == MOVE && Math.abs(itemCount - positionStart) == 1) { 660 // reverse of this is also true 661 if (itemCount == op.positionStart && positionStart == op.itemCount) { 662 return true; 663 } 664 } 665 if (itemCount != op.itemCount) { 666 return false; 667 } 668 if (positionStart != op.positionStart) { 669 return false; 670 } 671 672 return true; 673 } 674 675 @Override hashCode()676 public int hashCode() { 677 int result = cmd; 678 result = 31 * result + positionStart; 679 result = 31 * result + itemCount; 680 return result; 681 } 682 } 683 684 @Override obtainUpdateOp(int cmd, int positionStart, int itemCount)685 public UpdateOp obtainUpdateOp(int cmd, int positionStart, int itemCount) { 686 UpdateOp op = mUpdateOpPool.acquire(); 687 if (op == null) { 688 op = new UpdateOp(cmd, positionStart, itemCount); 689 } else { 690 op.cmd = cmd; 691 op.positionStart = positionStart; 692 op.itemCount = itemCount; 693 } 694 return op; 695 } 696 697 @Override recycleUpdateOp(UpdateOp op)698 public void recycleUpdateOp(UpdateOp op) { 699 if (!mDisableRecycler) { 700 mUpdateOpPool.release(op); 701 } 702 } 703 recycleUpdateOpsAndClearList(List<UpdateOp> ops)704 void recycleUpdateOpsAndClearList(List<UpdateOp> ops) { 705 final int count = ops.size(); 706 for (int i = 0; i < count; i++) { 707 recycleUpdateOp(ops.get(i)); 708 } 709 ops.clear(); 710 } 711 712 /** 713 * Contract between AdapterHelper and RecyclerView. 714 */ 715 static interface Callback { 716 findViewHolder(int position)717 ViewHolder findViewHolder(int position); 718 offsetPositionsForRemovingInvisible(int positionStart, int itemCount)719 void offsetPositionsForRemovingInvisible(int positionStart, int itemCount); 720 offsetPositionsForRemovingLaidOutOrNewView(int positionStart, int itemCount)721 void offsetPositionsForRemovingLaidOutOrNewView(int positionStart, int itemCount); 722 markViewHoldersUpdated(int positionStart, int itemCount)723 void markViewHoldersUpdated(int positionStart, int itemCount); 724 onDispatchFirstPass(UpdateOp updateOp)725 void onDispatchFirstPass(UpdateOp updateOp); 726 onDispatchSecondPass(UpdateOp updateOp)727 void onDispatchSecondPass(UpdateOp updateOp); 728 offsetPositionsForAdd(int positionStart, int itemCount)729 void offsetPositionsForAdd(int positionStart, int itemCount); 730 offsetPositionsForMove(int from, int to)731 void offsetPositionsForMove(int from, int to); 732 } 733 } 734