Home | History | Annotate | Download | only in widget
      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