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.util; 18 19import java.lang.reflect.Array; 20import java.util.Arrays; 21import java.util.Collection; 22import java.util.Comparator; 23 24/** 25 * A Sorted list implementation that can keep items in order and also notify for changes in the 26 * list 27 * such that it can be bound to a {@link android.support.v7.widget.RecyclerView.Adapter 28 * RecyclerView.Adapter}. 29 * <p> 30 * It keeps items ordered using the {@link Callback#compare(Object, Object)} method and uses 31 * binary search to retrieve items. If the sorting criteria of your items may change, make sure you 32 * call appropriate methods while editing them to avoid data inconsistencies. 33 * <p> 34 * You can control the order of items and change notifications via the {@link Callback} parameter. 35 */ 36@SuppressWarnings("unchecked") 37public class SortedList<T> { 38 39 /** 40 * Used by {@link #indexOf(Object)} when he item cannot be found in the list. 41 */ 42 public static final int INVALID_POSITION = -1; 43 44 private static final int MIN_CAPACITY = 10; 45 private static final int CAPACITY_GROWTH = MIN_CAPACITY; 46 private static final int INSERTION = 1; 47 private static final int DELETION = 1 << 1; 48 private static final int LOOKUP = 1 << 2; 49 T[] mData; 50 51 /** 52 * A copy of the previous list contents used during the merge phase of addAll. 53 */ 54 private T[] mOldData; 55 private int mOldDataStart; 56 private int mOldDataSize; 57 58 /** 59 * The size of the valid portion of mData during the merge phase of addAll. 60 */ 61 private int mMergedSize; 62 63 64 /** 65 * The callback instance that controls the behavior of the SortedList and get notified when 66 * changes happen. 67 */ 68 private Callback mCallback; 69 70 private BatchedCallback mBatchedCallback; 71 72 private int mSize; 73 private final Class<T> mTClass; 74 75 /** 76 * Creates a new SortedList of type T. 77 * 78 * @param klass The class of the contents of the SortedList. 79 * @param callback The callback that controls the behavior of SortedList. 80 */ 81 public SortedList(Class<T> klass, Callback<T> callback) { 82 this(klass, callback, MIN_CAPACITY); 83 } 84 85 /** 86 * Creates a new SortedList of type T. 87 * 88 * @param klass The class of the contents of the SortedList. 89 * @param callback The callback that controls the behavior of SortedList. 90 * @param initialCapacity The initial capacity to hold items. 91 */ 92 public SortedList(Class<T> klass, Callback<T> callback, int initialCapacity) { 93 mTClass = klass; 94 mData = (T[]) Array.newInstance(klass, initialCapacity); 95 mCallback = callback; 96 mSize = 0; 97 } 98 99 /** 100 * The number of items in the list. 101 * 102 * @return The number of items in the list. 103 */ 104 public int size() { 105 return mSize; 106 } 107 108 /** 109 * Adds the given item to the list. If this is a new item, SortedList calls 110 * {@link Callback#onInserted(int, int)}. 111 * <p> 112 * If the item already exists in the list and its sorting criteria is not changed, it is 113 * replaced with the existing Item. SortedList uses 114 * {@link Callback#areItemsTheSame(Object, Object)} to check if two items are the same item 115 * and uses {@link Callback#areContentsTheSame(Object, Object)} to decide whether it should 116 * call {@link Callback#onChanged(int, int)} or not. In both cases, it always removes the 117 * reference to the old item and puts the new item into the backing array even if 118 * {@link Callback#areContentsTheSame(Object, Object)} returns false. 119 * <p> 120 * If the sorting criteria of the item is changed, SortedList won't be able to find 121 * its duplicate in the list which will result in having a duplicate of the Item in the list. 122 * If you need to update sorting criteria of an item that already exists in the list, 123 * use {@link #updateItemAt(int, Object)}. You can find the index of the item using 124 * {@link #indexOf(Object)} before you update the object. 125 * 126 * @param item The item to be added into the list. 127 * 128 * @return The index of the newly added item. 129 * @see Callback#compare(Object, Object) 130 * @see Callback#areItemsTheSame(Object, Object) 131 * @see Callback#areContentsTheSame(Object, Object)} 132 */ 133 public int add(T item) { 134 throwIfMerging(); 135 return add(item, true); 136 } 137 138 /** 139 * Adds the given items to the list. Equivalent to calling {@link SortedList#add} in a loop, 140 * except the callback events may be in a different order/granularity since addAll can batch 141 * them for better performance. 142 * <p> 143 * If allowed, may modify the input array and even take the ownership over it in order 144 * to avoid extra memory allocation during sorting and deduplication. 145 * </p> 146 * @param items Array of items to be added into the list. 147 * @param mayModifyInput If true, SortedList is allowed to modify the input. 148 * @see SortedList#addAll(Object[] items) 149 */ 150 public void addAll(T[] items, boolean mayModifyInput) { 151 throwIfMerging(); 152 if (items.length == 0) { 153 return; 154 } 155 if (mayModifyInput) { 156 addAllInternal(items); 157 } else { 158 T[] copy = (T[]) Array.newInstance(mTClass, items.length); 159 System.arraycopy(items, 0, copy, 0, items.length); 160 addAllInternal(copy); 161 } 162 163 } 164 165 /** 166 * Adds the given items to the list. Does not modify the input. 167 * 168 * @see SortedList#addAll(T[] items, boolean mayModifyInput) 169 * 170 * @param items Array of items to be added into the list. 171 */ 172 public void addAll(T... items) { 173 addAll(items, false); 174 } 175 176 /** 177 * Adds the given items to the list. Does not modify the input. 178 * 179 * @see SortedList#addAll(T[] items, boolean mayModifyInput) 180 * 181 * @param items Collection of items to be added into the list. 182 */ 183 public void addAll(Collection<T> items) { 184 T[] copy = (T[]) Array.newInstance(mTClass, items.size()); 185 addAll(items.toArray(copy), true); 186 } 187 188 private void addAllInternal(T[] newItems) { 189 final boolean forceBatchedUpdates = !(mCallback instanceof BatchedCallback); 190 if (forceBatchedUpdates) { 191 beginBatchedUpdates(); 192 } 193 194 mOldData = mData; 195 mOldDataStart = 0; 196 mOldDataSize = mSize; 197 198 Arrays.sort(newItems, mCallback); // Arrays.sort is stable. 199 200 final int newSize = deduplicate(newItems); 201 if (mSize == 0) { 202 mData = newItems; 203 mSize = newSize; 204 mMergedSize = newSize; 205 mCallback.onInserted(0, newSize); 206 } else { 207 merge(newItems, newSize); 208 } 209 210 mOldData = null; 211 212 if (forceBatchedUpdates) { 213 endBatchedUpdates(); 214 } 215 } 216 217 /** 218 * Remove duplicate items, leaving only the last item from each group of "same" items. 219 * Move the remaining items to the beginning of the array. 220 * 221 * @return Number of deduplicated items at the beginning of the array. 222 */ 223 private int deduplicate(T[] items) { 224 if (items.length == 0) { 225 throw new IllegalArgumentException("Input array must be non-empty"); 226 } 227 228 // Keep track of the range of equal items at the end of the output. 229 // Start with the range containing just the first item. 230 int rangeStart = 0; 231 int rangeEnd = 1; 232 233 for (int i = 1; i < items.length; ++i) { 234 T currentItem = items[i]; 235 236 int compare = mCallback.compare(items[rangeStart], currentItem); 237 if (compare > 0) { 238 throw new IllegalArgumentException("Input must be sorted in ascending order."); 239 } 240 241 if (compare == 0) { 242 // The range of equal items continues, update it. 243 final int sameItemPos = findSameItem(currentItem, items, rangeStart, rangeEnd); 244 if (sameItemPos != INVALID_POSITION) { 245 // Replace the duplicate item. 246 items[sameItemPos] = currentItem; 247 } else { 248 // Expand the range. 249 if (rangeEnd != i) { // Avoid redundant copy. 250 items[rangeEnd] = currentItem; 251 } 252 rangeEnd++; 253 } 254 } else { 255 // The range has ended. Reset it to contain just the current item. 256 if (rangeEnd != i) { // Avoid redundant copy. 257 items[rangeEnd] = currentItem; 258 } 259 rangeStart = rangeEnd++; 260 } 261 } 262 return rangeEnd; 263 } 264 265 266 private int findSameItem(T item, T[] items, int from, int to) { 267 for (int pos = from; pos < to; pos++) { 268 if (mCallback.areItemsTheSame(items[pos], item)) { 269 return pos; 270 } 271 } 272 return INVALID_POSITION; 273 } 274 275 /** 276 * This method assumes that newItems are sorted and deduplicated. 277 */ 278 private void merge(T[] newData, int newDataSize) { 279 final int mergedCapacity = mSize + newDataSize + CAPACITY_GROWTH; 280 mData = (T[]) Array.newInstance(mTClass, mergedCapacity); 281 mMergedSize = 0; 282 283 int newDataStart = 0; 284 while (mOldDataStart < mOldDataSize || newDataStart < newDataSize) { 285 if (mOldDataStart == mOldDataSize) { 286 // No more old items, copy the remaining new items. 287 int itemCount = newDataSize - newDataStart; 288 System.arraycopy(newData, newDataStart, mData, mMergedSize, itemCount); 289 mMergedSize += itemCount; 290 mSize += itemCount; 291 mCallback.onInserted(mMergedSize - itemCount, itemCount); 292 break; 293 } 294 295 if (newDataStart == newDataSize) { 296 // No more new items, copy the remaining old items. 297 int itemCount = mOldDataSize - mOldDataStart; 298 System.arraycopy(mOldData, mOldDataStart, mData, mMergedSize, itemCount); 299 mMergedSize += itemCount; 300 break; 301 } 302 303 T oldItem = mOldData[mOldDataStart]; 304 T newItem = newData[newDataStart]; 305 int compare = mCallback.compare(oldItem, newItem); 306 if (compare > 0) { 307 // New item is lower, output it. 308 mData[mMergedSize++] = newItem; 309 mSize++; 310 newDataStart++; 311 mCallback.onInserted(mMergedSize - 1, 1); 312 } else if (compare == 0 && mCallback.areItemsTheSame(oldItem, newItem)) { 313 // Items are the same. Output the new item, but consume both. 314 mData[mMergedSize++] = newItem; 315 newDataStart++; 316 mOldDataStart++; 317 if (!mCallback.areContentsTheSame(oldItem, newItem)) { 318 mCallback.onChanged(mMergedSize - 1, 1); 319 } 320 } else { 321 // Old item is lower than or equal to (but not the same as the new). Output it. 322 // New item with the same sort order will be inserted later. 323 mData[mMergedSize++] = oldItem; 324 mOldDataStart++; 325 } 326 } 327 } 328 329 private void throwIfMerging() { 330 if (mOldData != null) { 331 throw new IllegalStateException("Cannot call this method from within addAll"); 332 } 333 } 334 335 /** 336 * Batches adapter updates that happen between calling this method until calling 337 * {@link #endBatchedUpdates()}. For example, if you add multiple items in a loop 338 * and they are placed into consecutive indices, SortedList calls 339 * {@link Callback#onInserted(int, int)} only once with the proper item count. If an event 340 * cannot be merged with the previous event, the previous event is dispatched 341 * to the callback instantly. 342 * <p> 343 * After running your data updates, you <b>must</b> call {@link #endBatchedUpdates()} 344 * which will dispatch any deferred data change event to the current callback. 345 * <p> 346 * A sample implementation may look like this: 347 * <pre> 348 * mSortedList.beginBatchedUpdates(); 349 * try { 350 * mSortedList.add(item1) 351 * mSortedList.add(item2) 352 * mSortedList.remove(item3) 353 * ... 354 * } finally { 355 * mSortedList.endBatchedUpdates(); 356 * } 357 * </pre> 358 * <p> 359 * Instead of using this method to batch calls, you can use a Callback that extends 360 * {@link BatchedCallback}. In that case, you must make sure that you are manually calling 361 * {@link BatchedCallback#dispatchLastEvent()} right after you complete your data changes. 362 * Failing to do so may create data inconsistencies with the Callback. 363 * <p> 364 * If the current Callback is an instance of {@link BatchedCallback}, calling this method 365 * has no effect. 366 */ 367 public void beginBatchedUpdates() { 368 throwIfMerging(); 369 if (mCallback instanceof BatchedCallback) { 370 return; 371 } 372 if (mBatchedCallback == null) { 373 mBatchedCallback = new BatchedCallback(mCallback); 374 } 375 mCallback = mBatchedCallback; 376 } 377 378 /** 379 * Ends the update transaction and dispatches any remaining event to the callback. 380 */ 381 public void endBatchedUpdates() { 382 throwIfMerging(); 383 if (mCallback instanceof BatchedCallback) { 384 ((BatchedCallback) mCallback).dispatchLastEvent(); 385 } 386 if (mCallback == mBatchedCallback) { 387 mCallback = mBatchedCallback.mWrappedCallback; 388 } 389 } 390 391 private int add(T item, boolean notify) { 392 int index = findIndexOf(item, mData, 0, mSize, INSERTION); 393 if (index == INVALID_POSITION) { 394 index = 0; 395 } else if (index < mSize) { 396 T existing = mData[index]; 397 if (mCallback.areItemsTheSame(existing, item)) { 398 if (mCallback.areContentsTheSame(existing, item)) { 399 //no change but still replace the item 400 mData[index] = item; 401 return index; 402 } else { 403 mData[index] = item; 404 mCallback.onChanged(index, 1); 405 return index; 406 } 407 } 408 } 409 addToData(index, item); 410 if (notify) { 411 mCallback.onInserted(index, 1); 412 } 413 return index; 414 } 415 416 /** 417 * Removes the provided item from the list and calls {@link Callback#onRemoved(int, int)}. 418 * 419 * @param item The item to be removed from the list. 420 * 421 * @return True if item is removed, false if item cannot be found in the list. 422 */ 423 public boolean remove(T item) { 424 throwIfMerging(); 425 return remove(item, true); 426 } 427 428 /** 429 * Removes the item at the given index and calls {@link Callback#onRemoved(int, int)}. 430 * 431 * @param index The index of the item to be removed. 432 * 433 * @return The removed item. 434 */ 435 public T removeItemAt(int index) { 436 throwIfMerging(); 437 T item = get(index); 438 removeItemAtIndex(index, true); 439 return item; 440 } 441 442 private boolean remove(T item, boolean notify) { 443 int index = findIndexOf(item, mData, 0, mSize, DELETION); 444 if (index == INVALID_POSITION) { 445 return false; 446 } 447 removeItemAtIndex(index, notify); 448 return true; 449 } 450 451 private void removeItemAtIndex(int index, boolean notify) { 452 System.arraycopy(mData, index + 1, mData, index, mSize - index - 1); 453 mSize--; 454 mData[mSize] = null; 455 if (notify) { 456 mCallback.onRemoved(index, 1); 457 } 458 } 459 460 /** 461 * Updates the item at the given index and calls {@link Callback#onChanged(int, int)} and/or 462 * {@link Callback#onMoved(int, int)} if necessary. 463 * <p> 464 * You can use this method if you need to change an existing Item such that its position in the 465 * list may change. 466 * <p> 467 * If the new object is a different object (<code>get(index) != item</code>) and 468 * {@link Callback#areContentsTheSame(Object, Object)} returns <code>true</code>, SortedList 469 * avoids calling {@link Callback#onChanged(int, int)} otherwise it calls 470 * {@link Callback#onChanged(int, int)}. 471 * <p> 472 * If the new position of the item is different than the provided <code>index</code>, 473 * SortedList 474 * calls {@link Callback#onMoved(int, int)}. 475 * 476 * @param index The index of the item to replace 477 * @param item The item to replace the item at the given Index. 478 * @see #add(Object) 479 */ 480 public void updateItemAt(int index, T item) { 481 throwIfMerging(); 482 final T existing = get(index); 483 // assume changed if the same object is given back 484 boolean contentsChanged = existing == item || !mCallback.areContentsTheSame(existing, item); 485 if (existing != item) { 486 // different items, we can use comparison and may avoid lookup 487 final int cmp = mCallback.compare(existing, item); 488 if (cmp == 0) { 489 mData[index] = item; 490 if (contentsChanged) { 491 mCallback.onChanged(index, 1); 492 } 493 return; 494 } 495 } 496 if (contentsChanged) { 497 mCallback.onChanged(index, 1); 498 } 499 // TODO this done in 1 pass to avoid shifting twice. 500 removeItemAtIndex(index, false); 501 int newIndex = add(item, false); 502 if (index != newIndex) { 503 mCallback.onMoved(index, newIndex); 504 } 505 } 506 507 /** 508 * This method can be used to recalculate the position of the item at the given index, without 509 * triggering an {@link Callback#onChanged(int, int)} callback. 510 * <p> 511 * If you are editing objects in the list such that their position in the list may change but 512 * you don't want to trigger an onChange animation, you can use this method to re-position it. 513 * If the item changes position, SortedList will call {@link Callback#onMoved(int, int)} 514 * without 515 * calling {@link Callback#onChanged(int, int)}. 516 * <p> 517 * A sample usage may look like: 518 * 519 * <pre> 520 * final int position = mSortedList.indexOf(item); 521 * item.incrementPriority(); // assume items are sorted by priority 522 * mSortedList.recalculatePositionOfItemAt(position); 523 * </pre> 524 * In the example above, because the sorting criteria of the item has been changed, 525 * mSortedList.indexOf(item) will not be able to find the item. This is why the code above 526 * first 527 * gets the position before editing the item, edits it and informs the SortedList that item 528 * should be repositioned. 529 * 530 * @param index The current index of the Item whose position should be re-calculated. 531 * @see #updateItemAt(int, Object) 532 * @see #add(Object) 533 */ 534 public void recalculatePositionOfItemAt(int index) { 535 throwIfMerging(); 536 // TODO can be improved 537 final T item = get(index); 538 removeItemAtIndex(index, false); 539 int newIndex = add(item, false); 540 if (index != newIndex) { 541 mCallback.onMoved(index, newIndex); 542 } 543 } 544 545 /** 546 * Returns the item at the given index. 547 * 548 * @param index The index of the item to retrieve. 549 * 550 * @return The item at the given index. 551 * @throws java.lang.IndexOutOfBoundsException if provided index is negative or larger than the 552 * size of the list. 553 */ 554 public T get(int index) throws IndexOutOfBoundsException { 555 if (index >= mSize || index < 0) { 556 throw new IndexOutOfBoundsException("Asked to get item at " + index + " but size is " 557 + mSize); 558 } 559 if (mOldData != null) { 560 // The call is made from a callback during addAll execution. The data is split 561 // between mData and mOldData. 562 if (index >= mMergedSize) { 563 return mOldData[index - mMergedSize + mOldDataStart]; 564 } 565 } 566 return mData[index]; 567 } 568 569 /** 570 * Returns the position of the provided item. 571 * 572 * @param item The item to query for position. 573 * 574 * @return The position of the provided item or {@link #INVALID_POSITION} if item is not in the 575 * list. 576 */ 577 public int indexOf(T item) { 578 if (mOldData != null) { 579 int index = findIndexOf(item, mData, 0, mMergedSize, LOOKUP); 580 if (index != INVALID_POSITION) { 581 return index; 582 } 583 index = findIndexOf(item, mOldData, mOldDataStart, mOldDataSize, LOOKUP); 584 if (index != INVALID_POSITION) { 585 return index - mOldDataStart + mMergedSize; 586 } 587 return INVALID_POSITION; 588 } 589 return findIndexOf(item, mData, 0, mSize, LOOKUP); 590 } 591 592 private int findIndexOf(T item, T[] mData, int left, int right, int reason) { 593 while (left < right) { 594 final int middle = (left + right) / 2; 595 T myItem = mData[middle]; 596 final int cmp = mCallback.compare(myItem, item); 597 if (cmp < 0) { 598 left = middle + 1; 599 } else if (cmp == 0) { 600 if (mCallback.areItemsTheSame(myItem, item)) { 601 return middle; 602 } else { 603 int exact = linearEqualitySearch(item, middle, left, right); 604 if (reason == INSERTION) { 605 return exact == INVALID_POSITION ? middle : exact; 606 } else { 607 return exact; 608 } 609 } 610 } else { 611 right = middle; 612 } 613 } 614 return reason == INSERTION ? left : INVALID_POSITION; 615 } 616 617 private int linearEqualitySearch(T item, int middle, int left, int right) { 618 // go left 619 for (int next = middle - 1; next >= left; next--) { 620 T nextItem = mData[next]; 621 int cmp = mCallback.compare(nextItem, item); 622 if (cmp != 0) { 623 break; 624 } 625 if (mCallback.areItemsTheSame(nextItem, item)) { 626 return next; 627 } 628 } 629 for (int next = middle + 1; next < right; next++) { 630 T nextItem = mData[next]; 631 int cmp = mCallback.compare(nextItem, item); 632 if (cmp != 0) { 633 break; 634 } 635 if (mCallback.areItemsTheSame(nextItem, item)) { 636 return next; 637 } 638 } 639 return INVALID_POSITION; 640 } 641 642 private void addToData(int index, T item) { 643 if (index > mSize) { 644 throw new IndexOutOfBoundsException( 645 "cannot add item to " + index + " because size is " + mSize); 646 } 647 if (mSize == mData.length) { 648 // we are at the limit enlarge 649 T[] newData = (T[]) Array.newInstance(mTClass, mData.length + CAPACITY_GROWTH); 650 System.arraycopy(mData, 0, newData, 0, index); 651 newData[index] = item; 652 System.arraycopy(mData, index, newData, index + 1, mSize - index); 653 mData = newData; 654 } else { 655 // just shift, we fit 656 System.arraycopy(mData, index, mData, index + 1, mSize - index); 657 mData[index] = item; 658 } 659 mSize++; 660 } 661 662 /** 663 * Removes all items from the SortedList. 664 */ 665 public void clear() { 666 throwIfMerging(); 667 if (mSize == 0) { 668 return; 669 } 670 final int prevSize = mSize; 671 Arrays.fill(mData, 0, prevSize, null); 672 mSize = 0; 673 mCallback.onRemoved(0, prevSize); 674 } 675 676 /** 677 * The class that controls the behavior of the {@link SortedList}. 678 * <p> 679 * It defines how items should be sorted and how duplicates should be handled. 680 * <p> 681 * SortedList calls the callback methods on this class to notify changes about the underlying 682 * data. 683 */ 684 public static abstract class Callback<T2> implements Comparator<T2>, ListUpdateCallback { 685 686 /** 687 * Similar to {@link java.util.Comparator#compare(Object, Object)}, should compare two and 688 * return how they should be ordered. 689 * 690 * @param o1 The first object to compare. 691 * @param o2 The second object to compare. 692 * 693 * @return a negative integer, zero, or a positive integer as the 694 * first argument is less than, equal to, or greater than the 695 * second. 696 */ 697 @Override 698 abstract public int compare(T2 o1, T2 o2); 699 700 /** 701 * Called by the SortedList when the item at the given position is updated. 702 * 703 * @param position The position of the item which has been updated. 704 * @param count The number of items which has changed. 705 */ 706 abstract public void onChanged(int position, int count); 707 708 @Override 709 public void onChanged(int position, int count, Object payload) { 710 onChanged(position, count); 711 } 712 713 /** 714 * Called by the SortedList when it wants to check whether two items have the same data 715 * or not. SortedList uses this information to decide whether it should call 716 * {@link #onChanged(int, int)} or not. 717 * <p> 718 * SortedList uses this method to check equality instead of {@link Object#equals(Object)} 719 * so 720 * that you can change its behavior depending on your UI. 721 * <p> 722 * For example, if you are using SortedList with a {@link android.support.v7.widget.RecyclerView.Adapter 723 * RecyclerView.Adapter}, you should 724 * return whether the items' visual representations are the same or not. 725 * 726 * @param oldItem The previous representation of the object. 727 * @param newItem The new object that replaces the previous one. 728 * 729 * @return True if the contents of the items are the same or false if they are different. 730 */ 731 abstract public boolean areContentsTheSame(T2 oldItem, T2 newItem); 732 733 /** 734 * Called by the SortedList to decide whether two object represent the same Item or not. 735 * <p> 736 * For example, if your items have unique ids, this method should check their equality. 737 * 738 * @param item1 The first item to check. 739 * @param item2 The second item to check. 740 * 741 * @return True if the two items represent the same object or false if they are different. 742 */ 743 abstract public boolean areItemsTheSame(T2 item1, T2 item2); 744 } 745 746 /** 747 * A callback implementation that can batch notify events dispatched by the SortedList. 748 * <p> 749 * This class can be useful if you want to do multiple operations on a SortedList but don't 750 * want to dispatch each event one by one, which may result in a performance issue. 751 * <p> 752 * For example, if you are going to add multiple items to a SortedList, BatchedCallback call 753 * convert individual <code>onInserted(index, 1)</code> calls into one 754 * <code>onInserted(index, N)</code> if items are added into consecutive indices. This change 755 * can help RecyclerView resolve changes much more easily. 756 * <p> 757 * If consecutive changes in the SortedList are not suitable for batching, BatchingCallback 758 * dispatches them as soon as such case is detected. After your edits on the SortedList is 759 * complete, you <b>must</b> always call {@link BatchedCallback#dispatchLastEvent()} to flush 760 * all changes to the Callback. 761 */ 762 public static class BatchedCallback<T2> extends Callback<T2> { 763 764 final Callback<T2> mWrappedCallback; 765 private final BatchingListUpdateCallback mBatchingListUpdateCallback; 766 /** 767 * Creates a new BatchedCallback that wraps the provided Callback. 768 * 769 * @param wrappedCallback The Callback which should received the data change callbacks. 770 * Other method calls (e.g. {@link #compare(Object, Object)} from 771 * the SortedList are directly forwarded to this Callback. 772 */ 773 public BatchedCallback(Callback<T2> wrappedCallback) { 774 mWrappedCallback = wrappedCallback; 775 mBatchingListUpdateCallback = new BatchingListUpdateCallback(mWrappedCallback); 776 } 777 778 @Override 779 public int compare(T2 o1, T2 o2) { 780 return mWrappedCallback.compare(o1, o2); 781 } 782 783 @Override 784 public void onInserted(int position, int count) { 785 mBatchingListUpdateCallback.onInserted(position, count); 786 } 787 788 @Override 789 public void onRemoved(int position, int count) { 790 mBatchingListUpdateCallback.onRemoved(position, count); 791 } 792 793 @Override 794 public void onMoved(int fromPosition, int toPosition) { 795 mBatchingListUpdateCallback.onMoved(fromPosition, toPosition); 796 } 797 798 @Override 799 public void onChanged(int position, int count) { 800 mBatchingListUpdateCallback.onChanged(position, count, null); 801 } 802 803 @Override 804 public boolean areContentsTheSame(T2 oldItem, T2 newItem) { 805 return mWrappedCallback.areContentsTheSame(oldItem, newItem); 806 } 807 808 @Override 809 public boolean areItemsTheSame(T2 item1, T2 item2) { 810 return mWrappedCallback.areItemsTheSame(item1, item2); 811 } 812 813 /** 814 * This method dispatches any pending event notifications to the wrapped Callback. 815 * You <b>must</b> always call this method after you are done with editing the SortedList. 816 */ 817 public void dispatchLastEvent() { 818 mBatchingListUpdateCallback.dispatchLastEvent(); 819 } 820 } 821} 822