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.content.Context;
     17 import android.graphics.Bitmap;
     18 import android.graphics.drawable.BitmapDrawable;
     19 import android.graphics.drawable.Drawable;
     20 
     21 import java.lang.ref.WeakReference;
     22 import java.util.ArrayList;
     23 import java.util.List;
     24 
     25 /**
     26  * An overview {@link Row} for a details fragment. This row consists of an image, a
     27  * description view, and optionally a series of {@link Action}s that can be taken for
     28  * the item.
     29  *
     30  * <h3>Actions</h3>
     31  * Application uses {@link #setActionsAdapter(ObjectAdapter)} to set actions on the overview
     32  * row.  {@link SparseArrayObjectAdapter} is recommended for easily updating actions while
     33  * maintaining the order.  The application can add or remove actions on the UI thread after the
     34  * row is bound to a view.
     35  *
     36  * <h3>Updating main item</h3>
     37  * After the row is bound to a view, the application may call {@link #setItem(Object)}
     38  * on UI thread and the view will be updated.
     39  *
     40  * <h3>Updating image</h3>
     41  * After the row is bound to view, the application may change the image by calling {@link
     42  * #setImageBitmap(Context, Bitmap)} or {@link #setImageDrawable(Drawable)} on the UI thread,
     43  * and the view will be updated.
     44  */
     45 public class DetailsOverviewRow extends Row {
     46 
     47     /**
     48      * Listener for changes of DetailsOverviewRow.
     49      */
     50     public static class Listener {
     51 
     52         /**
     53          * Called when DetailsOverviewRow has changed image drawable.
     54          */
     55         public void onImageDrawableChanged(DetailsOverviewRow row) {
     56         }
     57 
     58         /**
     59          * Called when DetailsOverviewRow has changed main item.
     60          */
     61         public void onItemChanged(DetailsOverviewRow row) {
     62         }
     63 
     64         /**
     65          * Called when DetailsOverviewRow has changed actions adapter.
     66          */
     67         public void onActionsAdapterChanged(DetailsOverviewRow row) {
     68         }
     69     }
     70 
     71     private Object mItem;
     72     private Drawable mImageDrawable;
     73     private boolean mImageScaleUpAllowed = true;
     74     private ArrayList<WeakReference<Listener>> mListeners;
     75     private PresenterSelector mDefaultActionPresenter = new ActionPresenterSelector();
     76     private ObjectAdapter mActionsAdapter = new ArrayObjectAdapter(mDefaultActionPresenter);
     77 
     78     /**
     79      * Constructor for a DetailsOverviewRow.
     80      *
     81      * @param item The main item for the details page.
     82      */
     83     public DetailsOverviewRow(Object item) {
     84         super(null);
     85         mItem = item;
     86         verify();
     87     }
     88 
     89     /**
     90      * Adds listener for the details page.
     91      */
     92     final void addListener(Listener listener) {
     93         if (mListeners == null) {
     94             mListeners = new ArrayList<WeakReference<Listener>>();
     95         } else {
     96             for (int i = 0; i < mListeners.size();) {
     97                 Listener l = mListeners.get(i).get();
     98                 if (l == null) {
     99                     mListeners.remove(i);
    100                 } else {
    101                     if (l == listener) {
    102                         return;
    103                     }
    104                     i++;
    105                 }
    106             }
    107         }
    108         mListeners.add(new WeakReference<Listener>(listener));
    109     }
    110 
    111     /**
    112      * Removes listener of the details page.
    113      */
    114     final void removeListener(Listener listener) {
    115         if (mListeners != null) {
    116             for (int i = 0; i < mListeners.size();) {
    117                 Listener l = mListeners.get(i).get();
    118                 if (l == null) {
    119                     mListeners.remove(i);
    120                 } else {
    121                     if (l == listener) {
    122                         mListeners.remove(i);
    123                         return;
    124                     }
    125                     i++;
    126                 }
    127             }
    128         }
    129     }
    130 
    131     /**
    132      * Notifies listeners for main item change on UI thread.
    133      */
    134     final void notifyItemChanged() {
    135         if (mListeners != null) {
    136             for (int i = 0; i < mListeners.size();) {
    137                 Listener l = mListeners.get(i).get();
    138                 if (l == null) {
    139                     mListeners.remove(i);
    140                 } else {
    141                     l.onItemChanged(this);
    142                     i++;
    143                 }
    144             }
    145         }
    146     }
    147 
    148     /**
    149      * Notifies listeners for image related change on UI thread.
    150      */
    151     final void notifyImageDrawableChanged() {
    152         if (mListeners != null) {
    153             for (int i = 0; i < mListeners.size();) {
    154                 Listener l = mListeners.get(i).get();
    155                 if (l == null) {
    156                     mListeners.remove(i);
    157                 } else {
    158                     l.onImageDrawableChanged(this);
    159                     i++;
    160                 }
    161             }
    162         }
    163     }
    164 
    165     /**
    166      * Notifies listeners for actions adapter changed on UI thread.
    167      */
    168     final void notifyActionsAdapterChanged() {
    169         if (mListeners != null) {
    170             for (int i = 0; i < mListeners.size();) {
    171                 Listener l = mListeners.get(i).get();
    172                 if (l == null) {
    173                     mListeners.remove(i);
    174                 } else {
    175                     l.onActionsAdapterChanged(this);
    176                     i++;
    177                 }
    178             }
    179         }
    180     }
    181 
    182     /**
    183      * Returns the main item for the details page.
    184      */
    185     public final Object getItem() {
    186         return mItem;
    187     }
    188 
    189     /**
    190      * Sets the main item for the details page.  Must be called on UI thread after
    191      * row is bound to view.
    192      */
    193     public final void setItem(Object item) {
    194         if (item != mItem) {
    195             mItem = item;
    196             notifyItemChanged();
    197         }
    198     }
    199 
    200     /**
    201      * Sets a drawable as the image of this details overview.  Must be called on UI thread
    202      * after row is bound to view.
    203      *
    204      * @param drawable The drawable to set.
    205      */
    206     public final void setImageDrawable(Drawable drawable) {
    207         if (mImageDrawable != drawable) {
    208             mImageDrawable = drawable;
    209             notifyImageDrawableChanged();
    210         }
    211     }
    212 
    213     /**
    214      * Sets a Bitmap as the image of this details overview.  Must be called on UI thread
    215      * after row is bound to view.
    216      *
    217      * @param context The context to retrieve display metrics from.
    218      * @param bm The bitmap to set.
    219      */
    220     public final void setImageBitmap(Context context, Bitmap bm) {
    221         mImageDrawable = new BitmapDrawable(context.getResources(), bm);
    222         notifyImageDrawableChanged();
    223     }
    224 
    225     /**
    226      * Returns the image drawable of this details overview.
    227      *
    228      * @return The overview's image drawable, or null if no drawable has been
    229      *         assigned.
    230      */
    231     public final Drawable getImageDrawable() {
    232         return mImageDrawable;
    233     }
    234 
    235     /**
    236      * Allows or disallows scaling up of images.
    237      * Images will always be scaled down if necessary.  Must be called on UI thread
    238      * after row is bound to view.
    239      */
    240     public void setImageScaleUpAllowed(boolean allowed) {
    241         if (allowed != mImageScaleUpAllowed) {
    242             mImageScaleUpAllowed = allowed;
    243             notifyImageDrawableChanged();
    244         }
    245     }
    246 
    247     /**
    248      * Returns true if the image may be scaled up; false otherwise.
    249      */
    250     public boolean isImageScaleUpAllowed() {
    251         return mImageScaleUpAllowed;
    252     }
    253 
    254     /**
    255      * Returns the actions adapter.  Throws ClassCastException if the current
    256      * actions adapter is not an instance of {@link ArrayObjectAdapter}.
    257      */
    258     private ArrayObjectAdapter getArrayObjectAdapter() {
    259         return (ArrayObjectAdapter) mActionsAdapter;
    260     }
    261 
    262     /**
    263      * Adds an Action to the overview. It will throw ClassCastException if the current actions
    264      * adapter is not an instance of {@link ArrayObjectAdapter}. Must be called on the UI thread.
    265      *
    266      * @param action The Action to add.
    267      * @deprecated Use {@link #setActionsAdapter(ObjectAdapter)} and {@link #getActionsAdapter()}
    268      */
    269     @Deprecated
    270     public final void addAction(Action action) {
    271         getArrayObjectAdapter().add(action);
    272     }
    273 
    274     /**
    275      * Adds an Action to the overview at the specified position. It will throw ClassCastException if
    276      * current actions adapter is not an instance of f{@link ArrayObjectAdapter}. Must be called
    277      * on the UI thread.
    278      *
    279      * @param pos The position to insert the Action.
    280      * @param action The Action to add.
    281      * @deprecated Use {@link #setActionsAdapter(ObjectAdapter)} and {@link #getActionsAdapter()}
    282      */
    283     @Deprecated
    284     public final void addAction(int pos, Action action) {
    285         getArrayObjectAdapter().add(pos, action);
    286     }
    287 
    288     /**
    289      * Removes the given Action from the overview. It will throw ClassCastException if current
    290      * actions adapter is not {@link ArrayObjectAdapter}. Must be called on UI thread.
    291      *
    292      * @param action The Action to remove.
    293      * @return true if the overview contained the specified Action.
    294      * @deprecated Use {@link #setActionsAdapter(ObjectAdapter)} and {@link #getActionsAdapter()}
    295      */
    296     @Deprecated
    297     public final boolean removeAction(Action action) {
    298         return getArrayObjectAdapter().remove(action);
    299     }
    300 
    301     /**
    302      * Returns a read-only view of the list of Actions of this details overview. It will throw
    303      * ClassCastException if current actions adapter is not {@link ArrayObjectAdapter}. Must be
    304      * called on UI thread.
    305      *
    306      * @return An unmodifiable view of the list of Actions.
    307      * @deprecated Use {@link #setActionsAdapter(ObjectAdapter)} and {@link #getActionsAdapter()}
    308      */
    309     @Deprecated
    310     public final List<Action> getActions() {
    311         return getArrayObjectAdapter().unmodifiableList();
    312     }
    313 
    314     /**
    315      * Returns the {@link ObjectAdapter} for actions.
    316      */
    317     public final ObjectAdapter getActionsAdapter() {
    318         return mActionsAdapter;
    319     }
    320 
    321     /**
    322      * Sets the {@link ObjectAdapter} for actions.  A default {@link PresenterSelector} will be
    323      * attached to the adapter if it doesn't have one.
    324      *
    325      * @param adapter  Adapter for actions.
    326      */
    327     public final void setActionsAdapter(ObjectAdapter adapter) {
    328         if (adapter != mActionsAdapter) {
    329             mActionsAdapter = adapter;
    330             if (mActionsAdapter.getPresenterSelector() == null) {
    331                 mActionsAdapter.setPresenterSelector(mDefaultActionPresenter);
    332             }
    333             notifyActionsAdapterChanged();
    334         }
    335     }
    336 
    337     /**
    338      * Returns the Action associated with the given keycode, or null if no associated action exists.
    339      */
    340     public Action getActionForKeyCode(int keyCode) {
    341         ObjectAdapter adapter = getActionsAdapter();
    342         if (adapter != null) {
    343             for (int i = 0; i < adapter.size(); i++) {
    344                 Action action = (Action) adapter.get(i);
    345                 if (action.respondsToKeyCode(keyCode)) {
    346                     return action;
    347                 }
    348             }
    349         }
    350         return null;
    351     }
    352 
    353     private void verify() {
    354         if (mItem == null) {
    355             throw new IllegalArgumentException("Object cannot be null");
    356         }
    357     }
    358 }
    359