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 static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
     17 
     18 import android.database.Observable;
     19 
     20 import androidx.annotation.RestrictTo;
     21 
     22 /**
     23  * Base class adapter to be used in leanback activities.  Provides access to a data model and is
     24  * decoupled from the presentation of the items via {@link PresenterSelector}.
     25  */
     26 public abstract class ObjectAdapter {
     27 
     28     /** Indicates that an id has not been set. */
     29     public static final int NO_ID = -1;
     30 
     31     /**
     32      * A DataObserver can be notified when an ObjectAdapter's underlying data
     33      * changes. Separate methods provide notifications about different types of
     34      * changes.
     35      */
     36     public static abstract class DataObserver {
     37         /**
     38          * Called whenever the ObjectAdapter's data has changed in some manner
     39          * outside of the set of changes covered by the other range-based change
     40          * notification methods.
     41          */
     42         public void onChanged() {
     43         }
     44 
     45         /**
     46          * Called when a range of items in the ObjectAdapter has changed. The
     47          * basic ordering and structure of the ObjectAdapter has not changed.
     48          *
     49          * @param positionStart The position of the first item that changed.
     50          * @param itemCount     The number of items changed.
     51          */
     52         public void onItemRangeChanged(int positionStart, int itemCount) {
     53             onChanged();
     54         }
     55 
     56         /**
     57          * Called when a range of items in the ObjectAdapter has changed. The
     58          * basic ordering and structure of the ObjectAdapter has not changed.
     59          *
     60          * @param positionStart The position of the first item that changed.
     61          * @param itemCount     The number of items changed.
     62          * @param payload       Optional parameter, use null to identify a "full" update.
     63          */
     64         public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
     65             onChanged();
     66         }
     67 
     68         /**
     69          * Called when a range of items is inserted into the ObjectAdapter.
     70          *
     71          * @param positionStart The position of the first inserted item.
     72          * @param itemCount     The number of items inserted.
     73          */
     74         public void onItemRangeInserted(int positionStart, int itemCount) {
     75             onChanged();
     76         }
     77 
     78         /**
     79          * Called when an item is moved from one position to another position
     80          *
     81          * @param fromPosition Previous position of the item.
     82          * @param toPosition   New position of the item.
     83          */
     84         public void onItemMoved(int fromPosition, int toPosition) {
     85             onChanged();
     86         }
     87 
     88         /**
     89          * Called when a range of items is removed from the ObjectAdapter.
     90          *
     91          * @param positionStart The position of the first removed item.
     92          * @param itemCount     The number of items removed.
     93          */
     94         public void onItemRangeRemoved(int positionStart, int itemCount) {
     95             onChanged();
     96         }
     97     }
     98 
     99     private static final class DataObservable extends Observable<DataObserver> {
    100 
    101         DataObservable() {
    102         }
    103 
    104         public void notifyChanged() {
    105             for (int i = mObservers.size() - 1; i >= 0; i--) {
    106                 mObservers.get(i).onChanged();
    107             }
    108         }
    109 
    110         public void notifyItemRangeChanged(int positionStart, int itemCount) {
    111             for (int i = mObservers.size() - 1; i >= 0; i--) {
    112                 mObservers.get(i).onItemRangeChanged(positionStart, itemCount);
    113             }
    114         }
    115 
    116         public void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
    117             for (int i = mObservers.size() - 1; i >= 0; i--) {
    118                 mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload);
    119             }
    120         }
    121 
    122         public void notifyItemRangeInserted(int positionStart, int itemCount) {
    123             for (int i = mObservers.size() - 1; i >= 0; i--) {
    124                 mObservers.get(i).onItemRangeInserted(positionStart, itemCount);
    125             }
    126         }
    127 
    128         public void notifyItemRangeRemoved(int positionStart, int itemCount) {
    129             for (int i = mObservers.size() - 1; i >= 0; i--) {
    130                 mObservers.get(i).onItemRangeRemoved(positionStart, itemCount);
    131             }
    132         }
    133 
    134         public void notifyItemMoved(int positionStart, int toPosition) {
    135             for (int i = mObservers.size() - 1; i >= 0; i--) {
    136                 mObservers.get(i).onItemMoved(positionStart, toPosition);
    137             }
    138         }
    139 
    140         boolean hasObserver() {
    141             return mObservers.size() > 0;
    142         }
    143     }
    144 
    145     private final DataObservable mObservable = new DataObservable();
    146     private boolean mHasStableIds;
    147     private PresenterSelector mPresenterSelector;
    148 
    149     /**
    150      * Constructs an adapter with the given {@link PresenterSelector}.
    151      */
    152     public ObjectAdapter(PresenterSelector presenterSelector) {
    153         setPresenterSelector(presenterSelector);
    154     }
    155 
    156     /**
    157      * Constructs an adapter that uses the given {@link Presenter} for all items.
    158      */
    159     public ObjectAdapter(Presenter presenter) {
    160         setPresenterSelector(new SinglePresenterSelector(presenter));
    161     }
    162 
    163     /**
    164      * Constructs an adapter.
    165      */
    166     public ObjectAdapter() {
    167     }
    168 
    169     /**
    170      * Sets the presenter selector.  May not be null.
    171      */
    172     public final void setPresenterSelector(PresenterSelector presenterSelector) {
    173         if (presenterSelector == null) {
    174             throw new IllegalArgumentException("Presenter selector must not be null");
    175         }
    176         final boolean update = (mPresenterSelector != null);
    177         final boolean selectorChanged = update && mPresenterSelector != presenterSelector;
    178 
    179         mPresenterSelector = presenterSelector;
    180 
    181         if (selectorChanged) {
    182             onPresenterSelectorChanged();
    183         }
    184         if (update) {
    185             notifyChanged();
    186         }
    187     }
    188 
    189     /**
    190      * Called when {@link #setPresenterSelector(PresenterSelector)} is called
    191      * and the PresenterSelector differs from the previous one.
    192      */
    193     protected void onPresenterSelectorChanged() {
    194     }
    195 
    196     /**
    197      * Returns the presenter selector for this ObjectAdapter.
    198      */
    199     public final PresenterSelector getPresenterSelector() {
    200         return mPresenterSelector;
    201     }
    202 
    203     /**
    204      * Registers a DataObserver for data change notifications.
    205      */
    206     public final void registerObserver(DataObserver observer) {
    207         mObservable.registerObserver(observer);
    208     }
    209 
    210     /**
    211      * Unregisters a DataObserver for data change notifications.
    212      */
    213     public final void unregisterObserver(DataObserver observer) {
    214         mObservable.unregisterObserver(observer);
    215     }
    216 
    217     /**
    218      * @hide
    219      */
    220     @RestrictTo(LIBRARY_GROUP)
    221     public final boolean hasObserver() {
    222         return mObservable.hasObserver();
    223     }
    224 
    225     /**
    226      * Unregisters all DataObservers for this ObjectAdapter.
    227      */
    228     public final void unregisterAllObservers() {
    229         mObservable.unregisterAll();
    230     }
    231 
    232     /**
    233      * Notifies UI that some items has changed.
    234      *
    235      * @param positionStart Starting position of the changed items.
    236      * @param itemCount     Total number of items that changed.
    237      */
    238     public final void notifyItemRangeChanged(int positionStart, int itemCount) {
    239         mObservable.notifyItemRangeChanged(positionStart, itemCount);
    240     }
    241 
    242     /**
    243      * Notifies UI that some items has changed.
    244      *
    245      * @param positionStart Starting position of the changed items.
    246      * @param itemCount     Total number of items that changed.
    247      * @param payload       Optional parameter, use null to identify a "full" update.
    248      */
    249     public final void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
    250         mObservable.notifyItemRangeChanged(positionStart, itemCount, payload);
    251     }
    252 
    253     /**
    254      * Notifies UI that new items has been inserted.
    255      *
    256      * @param positionStart Position where new items has been inserted.
    257      * @param itemCount     Count of the new items has been inserted.
    258      */
    259     final protected void notifyItemRangeInserted(int positionStart, int itemCount) {
    260         mObservable.notifyItemRangeInserted(positionStart, itemCount);
    261     }
    262 
    263     /**
    264      * Notifies UI that some items that has been removed.
    265      *
    266      * @param positionStart Starting position of the removed items.
    267      * @param itemCount     Total number of items that has been removed.
    268      */
    269     final protected void notifyItemRangeRemoved(int positionStart, int itemCount) {
    270         mObservable.notifyItemRangeRemoved(positionStart, itemCount);
    271     }
    272 
    273     /**
    274      * Notifies UI that item at fromPosition has been moved to toPosition.
    275      *
    276      * @param fromPosition Previous position of the item.
    277      * @param toPosition   New position of the item.
    278      */
    279     protected final void notifyItemMoved(int fromPosition, int toPosition) {
    280         mObservable.notifyItemMoved(fromPosition, toPosition);
    281     }
    282 
    283     /**
    284      * Notifies UI that the underlying data has changed.
    285      */
    286     final protected void notifyChanged() {
    287         mObservable.notifyChanged();
    288     }
    289 
    290     /**
    291      * Returns true if the item ids are stable across changes to the
    292      * underlying data.  When this is true, clients of the ObjectAdapter can use
    293      * {@link #getId(int)} to correlate Objects across changes.
    294      */
    295     public final boolean hasStableIds() {
    296         return mHasStableIds;
    297     }
    298 
    299     /**
    300      * Sets whether the item ids are stable across changes to the underlying
    301      * data.
    302      */
    303     public final void setHasStableIds(boolean hasStableIds) {
    304         boolean changed = mHasStableIds != hasStableIds;
    305         mHasStableIds = hasStableIds;
    306 
    307         if (changed) {
    308             onHasStableIdsChanged();
    309         }
    310     }
    311 
    312     /**
    313      * Called when {@link #setHasStableIds(boolean)} is called and the status
    314      * of stable ids has changed.
    315      */
    316     protected void onHasStableIdsChanged() {
    317     }
    318 
    319     /**
    320      * Returns the {@link Presenter} for the given item from the adapter.
    321      */
    322     public final Presenter getPresenter(Object item) {
    323         if (mPresenterSelector == null) {
    324             throw new IllegalStateException("Presenter selector must not be null");
    325         }
    326         return mPresenterSelector.getPresenter(item);
    327     }
    328 
    329     /**
    330      * Returns the number of items in the adapter.
    331      */
    332     public abstract int size();
    333 
    334     /**
    335      * Returns the item for the given position.
    336      */
    337     public abstract Object get(int position);
    338 
    339     /**
    340      * Returns the id for the given position.
    341      */
    342     public long getId(int position) {
    343         return NO_ID;
    344     }
    345 
    346     /**
    347      * Returns true if the adapter pairs each underlying data change with a call to notify and
    348      * false otherwise.
    349      */
    350     public boolean isImmediateNotifySupported() {
    351         return false;
    352     }
    353 }
    354