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