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