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