1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14 package androidx.leanback.widget; 15 16 import android.util.Log; 17 18 import androidx.annotation.Nullable; 19 import androidx.recyclerview.widget.DiffUtil; 20 import androidx.recyclerview.widget.ListUpdateCallback; 21 22 import java.util.ArrayList; 23 import java.util.Collection; 24 import java.util.Collections; 25 import java.util.List; 26 27 /** 28 * An {@link ObjectAdapter} implemented with an {@link ArrayList}. 29 */ 30 public class ArrayObjectAdapter extends ObjectAdapter { 31 32 private static final Boolean DEBUG = false; 33 private static final String TAG = "ArrayObjectAdapter"; 34 35 private final List mItems = new ArrayList<Object>(); 36 37 // To compute the payload correctly, we should use a temporary list to hold all the old items. 38 private final List mOldItems = new ArrayList<Object>(); 39 40 // Un modifiable version of mItems; 41 private List mUnmodifiableItems; 42 43 /** 44 * Constructs an adapter with the given {@link PresenterSelector}. 45 */ 46 public ArrayObjectAdapter(PresenterSelector presenterSelector) { 47 super(presenterSelector); 48 } 49 50 /** 51 * Constructs an adapter that uses the given {@link Presenter} for all items. 52 */ 53 public ArrayObjectAdapter(Presenter presenter) { 54 super(presenter); 55 } 56 57 /** 58 * Constructs an adapter. 59 */ 60 public ArrayObjectAdapter() { 61 super(); 62 } 63 64 @Override 65 public int size() { 66 return mItems.size(); 67 } 68 69 @Override 70 public Object get(int index) { 71 return mItems.get(index); 72 } 73 74 /** 75 * Returns the index for the first occurrence of item in the adapter, or -1 if 76 * not found. 77 * 78 * @param item The item to find in the list. 79 * @return Index of the first occurrence of the item in the adapter, or -1 80 * if not found. 81 */ 82 public int indexOf(Object item) { 83 return mItems.indexOf(item); 84 } 85 86 /** 87 * Notify that the content of a range of items changed. Note that this is 88 * not same as items being added or removed. 89 * 90 * @param positionStart The position of first item that has changed. 91 * @param itemCount The count of how many items have changed. 92 */ 93 public void notifyArrayItemRangeChanged(int positionStart, int itemCount) { 94 notifyItemRangeChanged(positionStart, itemCount); 95 } 96 97 /** 98 * Adds an item to the end of the adapter. 99 * 100 * @param item The item to add to the end of the adapter. 101 */ 102 public void add(Object item) { 103 add(mItems.size(), item); 104 } 105 106 /** 107 * Inserts an item into this adapter at the specified index. 108 * If the index is > {@link #size} an exception will be thrown. 109 * 110 * @param index The index at which the item should be inserted. 111 * @param item The item to insert into the adapter. 112 */ 113 public void add(int index, Object item) { 114 mItems.add(index, item); 115 notifyItemRangeInserted(index, 1); 116 } 117 118 /** 119 * Adds the objects in the given collection to the adapter, starting at the 120 * given index. If the index is >= {@link #size} an exception will be thrown. 121 * 122 * @param index The index at which the items should be inserted. 123 * @param items A {@link Collection} of items to insert. 124 */ 125 public void addAll(int index, Collection items) { 126 int itemsCount = items.size(); 127 if (itemsCount == 0) { 128 return; 129 } 130 mItems.addAll(index, items); 131 notifyItemRangeInserted(index, itemsCount); 132 } 133 134 /** 135 * Removes the first occurrence of the given item from the adapter. 136 * 137 * @param item The item to remove from the adapter. 138 * @return True if the item was found and thus removed from the adapter. 139 */ 140 public boolean remove(Object item) { 141 int index = mItems.indexOf(item); 142 if (index >= 0) { 143 mItems.remove(index); 144 notifyItemRangeRemoved(index, 1); 145 } 146 return index >= 0; 147 } 148 149 /** 150 * Moved the item at fromPosition to toPosition. 151 * 152 * @param fromPosition Previous position of the item. 153 * @param toPosition New position of the item. 154 */ 155 public void move(int fromPosition, int toPosition) { 156 if (fromPosition == toPosition) { 157 // no-op 158 return; 159 } 160 Object item = mItems.remove(fromPosition); 161 mItems.add(toPosition, item); 162 notifyItemMoved(fromPosition, toPosition); 163 } 164 165 /** 166 * Replaces item at position with a new item and calls notifyItemRangeChanged() 167 * at the given position. Note that this method does not compare new item to 168 * existing item. 169 * 170 * @param position The index of item to replace. 171 * @param item The new item to be placed at given position. 172 */ 173 public void replace(int position, Object item) { 174 mItems.set(position, item); 175 notifyItemRangeChanged(position, 1); 176 } 177 178 /** 179 * Removes a range of items from the adapter. The range is specified by giving 180 * the starting position and the number of elements to remove. 181 * 182 * @param position The index of the first item to remove. 183 * @param count The number of items to remove. 184 * @return The number of items removed. 185 */ 186 public int removeItems(int position, int count) { 187 int itemsToRemove = Math.min(count, mItems.size() - position); 188 if (itemsToRemove <= 0) { 189 return 0; 190 } 191 192 for (int i = 0; i < itemsToRemove; i++) { 193 mItems.remove(position); 194 } 195 notifyItemRangeRemoved(position, itemsToRemove); 196 return itemsToRemove; 197 } 198 199 /** 200 * Removes all items from this adapter, leaving it empty. 201 */ 202 public void clear() { 203 int itemCount = mItems.size(); 204 if (itemCount == 0) { 205 return; 206 } 207 mItems.clear(); 208 notifyItemRangeRemoved(0, itemCount); 209 } 210 211 /** 212 * Gets a read-only view of the list of object of this ArrayObjectAdapter. 213 */ 214 public <E> List<E> unmodifiableList() { 215 216 // The mUnmodifiableItems will only be created once as long as the content of mItems has not 217 // been changed. 218 if (mUnmodifiableItems == null) { 219 mUnmodifiableItems = Collections.unmodifiableList(mItems); 220 } 221 return mUnmodifiableItems; 222 } 223 224 @Override 225 public boolean isImmediateNotifySupported() { 226 return true; 227 } 228 229 ListUpdateCallback mListUpdateCallback; 230 231 /** 232 * Set a new item list to adapter. The DiffUtil will compute the difference and dispatch it to 233 * specified position. 234 * 235 * @param itemList List of new Items 236 * @param callback Optional DiffCallback Object to compute the difference between the old data 237 * set and new data set. When null, {@link #notifyChanged()} will be fired. 238 */ 239 public void setItems(final List itemList, final DiffCallback callback) { 240 if (callback == null) { 241 // shortcut when DiffCallback is not provided 242 mItems.clear(); 243 mItems.addAll(itemList); 244 notifyChanged(); 245 return; 246 } 247 mOldItems.clear(); 248 mOldItems.addAll(mItems); 249 250 DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() { 251 @Override 252 public int getOldListSize() { 253 return mOldItems.size(); 254 } 255 256 @Override 257 public int getNewListSize() { 258 return itemList.size(); 259 } 260 261 @Override 262 public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { 263 return callback.areItemsTheSame(mOldItems.get(oldItemPosition), 264 itemList.get(newItemPosition)); 265 } 266 267 @Override 268 public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { 269 return callback.areContentsTheSame(mOldItems.get(oldItemPosition), 270 itemList.get(newItemPosition)); 271 } 272 273 @Nullable 274 @Override 275 public Object getChangePayload(int oldItemPosition, int newItemPosition) { 276 return callback.getChangePayload(mOldItems.get(oldItemPosition), 277 itemList.get(newItemPosition)); 278 } 279 }); 280 281 // update items. 282 mItems.clear(); 283 mItems.addAll(itemList); 284 285 // dispatch diff result 286 if (mListUpdateCallback == null) { 287 mListUpdateCallback = new ListUpdateCallback() { 288 289 @Override 290 public void onInserted(int position, int count) { 291 if (DEBUG) { 292 Log.d(TAG, "onInserted"); 293 } 294 notifyItemRangeInserted(position, count); 295 } 296 297 @Override 298 public void onRemoved(int position, int count) { 299 if (DEBUG) { 300 Log.d(TAG, "onRemoved"); 301 } 302 notifyItemRangeRemoved(position, count); 303 } 304 305 @Override 306 public void onMoved(int fromPosition, int toPosition) { 307 if (DEBUG) { 308 Log.d(TAG, "onMoved"); 309 } 310 notifyItemMoved(fromPosition, toPosition); 311 } 312 313 @Override 314 public void onChanged(int position, int count, Object payload) { 315 if (DEBUG) { 316 Log.d(TAG, "onChanged"); 317 } 318 notifyItemRangeChanged(position, count, payload); 319 } 320 }; 321 } 322 diffResult.dispatchUpdatesTo(mListUpdateCallback); 323 mOldItems.clear(); 324 } 325 } 326