1/* 2 * Copyright 2018 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 */ 16package androidx.recyclerview.widget; 17 18import androidx.annotation.NonNull; 19 20/** 21 * Wraps a {@link ListUpdateCallback} callback and batches operations that can be merged. 22 * <p> 23 * For instance, when 2 add operations comes that adds 2 consecutive elements, 24 * BatchingListUpdateCallback merges them and calls the wrapped callback only once. 25 * <p> 26 * This is a general purpose class and is also used by 27 * {@link DiffUtil.DiffResult DiffResult} and 28 * {@link SortedList} to minimize the number of updates that are dispatched. 29 * <p> 30 * If you use this class to batch updates, you must call {@link #dispatchLastEvent()} when the 31 * stream of update events drain. 32 */ 33public class BatchingListUpdateCallback implements ListUpdateCallback { 34 private static final int TYPE_NONE = 0; 35 private static final int TYPE_ADD = 1; 36 private static final int TYPE_REMOVE = 2; 37 private static final int TYPE_CHANGE = 3; 38 39 final ListUpdateCallback mWrapped; 40 41 int mLastEventType = TYPE_NONE; 42 int mLastEventPosition = -1; 43 int mLastEventCount = -1; 44 Object mLastEventPayload = null; 45 46 public BatchingListUpdateCallback(@NonNull ListUpdateCallback callback) { 47 mWrapped = callback; 48 } 49 50 /** 51 * BatchingListUpdateCallback holds onto the last event to see if it can be merged with the 52 * next one. When stream of events finish, you should call this method to dispatch the last 53 * event. 54 */ 55 public void dispatchLastEvent() { 56 if (mLastEventType == TYPE_NONE) { 57 return; 58 } 59 switch (mLastEventType) { 60 case TYPE_ADD: 61 mWrapped.onInserted(mLastEventPosition, mLastEventCount); 62 break; 63 case TYPE_REMOVE: 64 mWrapped.onRemoved(mLastEventPosition, mLastEventCount); 65 break; 66 case TYPE_CHANGE: 67 mWrapped.onChanged(mLastEventPosition, mLastEventCount, mLastEventPayload); 68 break; 69 } 70 mLastEventPayload = null; 71 mLastEventType = TYPE_NONE; 72 } 73 74 @Override 75 public void onInserted(int position, int count) { 76 if (mLastEventType == TYPE_ADD && position >= mLastEventPosition 77 && position <= mLastEventPosition + mLastEventCount) { 78 mLastEventCount += count; 79 mLastEventPosition = Math.min(position, mLastEventPosition); 80 return; 81 } 82 dispatchLastEvent(); 83 mLastEventPosition = position; 84 mLastEventCount = count; 85 mLastEventType = TYPE_ADD; 86 } 87 88 @Override 89 public void onRemoved(int position, int count) { 90 if (mLastEventType == TYPE_REMOVE && mLastEventPosition >= position && 91 mLastEventPosition <= position + count) { 92 mLastEventCount += count; 93 mLastEventPosition = position; 94 return; 95 } 96 dispatchLastEvent(); 97 mLastEventPosition = position; 98 mLastEventCount = count; 99 mLastEventType = TYPE_REMOVE; 100 } 101 102 @Override 103 public void onMoved(int fromPosition, int toPosition) { 104 dispatchLastEvent(); // moves are not merged 105 mWrapped.onMoved(fromPosition, toPosition); 106 } 107 108 @Override 109 public void onChanged(int position, int count, Object payload) { 110 if (mLastEventType == TYPE_CHANGE && 111 !(position > mLastEventPosition + mLastEventCount 112 || position + count < mLastEventPosition || mLastEventPayload != payload)) { 113 // take potential overlap into account 114 int previousEnd = mLastEventPosition + mLastEventCount; 115 mLastEventPosition = Math.min(position, mLastEventPosition); 116 mLastEventCount = Math.max(previousEnd, position + count) - mLastEventPosition; 117 return; 118 } 119 dispatchLastEvent(); 120 mLastEventPosition = position; 121 mLastEventCount = count; 122 mLastEventPayload = payload; 123 mLastEventType = TYPE_CHANGE; 124 } 125} 126