DetailsOverviewRow.java revision 60bb6af2e336072921f5d3c3861e86b3cc6241b3
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 */
14package android.support.v17.leanback.widget;
15
16import android.content.Context;
17import android.graphics.Bitmap;
18import android.graphics.drawable.BitmapDrawable;
19import android.graphics.drawable.Drawable;
20import android.view.KeyEvent;
21
22import java.lang.ref.WeakReference;
23import java.util.ArrayList;
24import java.util.List;
25
26/**
27 * An overview 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 easy updating actions while
34 * keeping the order.  Application can add or remove actions on UI thread after the row is
35 * bound to view.
36 *
37 * <h3>Updating main item</h3>
38 * After the row is bound to view, application still can call ({@link #setItem(Object)})
39 * on UI thread.
40 *
41 * <h3>Updating image</h3>
42 * After the row is bound to view, application still can change image by calling ({@link
43 * #setImageBitmap(Context, Bitmap)}) or {@link #setImageDrawable(Drawable)}) on UI thread.
44 */
45public class DetailsOverviewRow extends Row {
46
47    /**
48     * Listener for changes of DetailsOverViewRow.
49     */
50    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     * Gets 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     * Gets 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     * Get array object adapter.  Throws ClassCastException if the current ObjectAdapter is not
256     * ArrayObjectAdapter.
257     */
258    private ArrayObjectAdapter getArrayObjectAdapter() {
259        return (ArrayObjectAdapter) mActionsAdapter;
260    }
261
262    /**
263     * Add an Action to the overview. It will throw ClassCastException if current actions adapter
264     * is not {@link ArrayObjectAdapter}. Must be called on UI thread.
265     *
266     * @param action The Action to add.
267     * @deprecated Use {@link #setActionsAdapter(ObjectAdapter)} and {@link #getActionsAdapter()}
268     */
269    public final void addAction(Action action) {
270        getArrayObjectAdapter().add(action);
271    }
272
273    /**
274     * Add an Action to the overview at the specified position. It will throw ClassCastException if
275     * current actions adapter is not {@link ArrayObjectAdapter}. Must be called on UI thread.
276     *
277     * @param pos The position to insert the Action.
278     * @param action The Action to add.
279     * @deprecated Use {@link #setActionsAdapter(ObjectAdapter)} and {@link #getActionsAdapter()}
280     */
281    public final void addAction(int pos, Action action) {
282        getArrayObjectAdapter().add(pos, action);
283    }
284
285    /**
286     * Remove the given Action from the overview. It will throw ClassCastException if current
287     * actions adapter is not {@link ArrayObjectAdapter}. Must be called on UI thread.
288     *
289     * @param action The Action to remove.
290     * @return true if the overview contained the specified Action.
291     * @deprecated Use {@link #setActionsAdapter(ObjectAdapter)} and {@link #getActionsAdapter()}
292     */
293    public final boolean removeAction(Action action) {
294        return getArrayObjectAdapter().remove(action);
295    }
296
297    /**
298     * Gets a read-only view of the list of Actions of this details overview. It will throw
299     * ClassCastException if current actions adapter is not {@link ArrayObjectAdapter}. Must be
300     * called on UI thread.
301     *
302     * @return An unmodifiable view of the list of Actions.
303     * @deprecated Use {@link #setActionsAdapter(ObjectAdapter)} and {@link #getActionsAdapter()}
304     */
305    public final List<Action> getActions() {
306        return getArrayObjectAdapter().unmodifiableList();
307    }
308
309    /**
310     * Gets {@link ObjectAdapter} for actions.
311     */
312    public final ObjectAdapter getActionsAdapter() {
313        return mActionsAdapter;
314    }
315
316    /**
317     * Sets {@link ObjectAdapter} for actions.
318     * @param adapter  Adapter for actions, a default {@link PresenterSelector} will be attached
319     *                 to the adapter if it doesn't have one.
320     */
321    public final void setActionsAdapter(ObjectAdapter adapter) {
322        if (adapter != mActionsAdapter) {
323            mActionsAdapter = adapter;
324            if (mActionsAdapter.getPresenterSelector() == null) {
325                mActionsAdapter.setPresenterSelector(mDefaultActionPresenter);
326            }
327            notifyActionsAdapterChanged();
328        }
329    }
330
331    /**
332     * Returns the Action associated with the given keycode, or null if no associated action exists.
333     */
334    public Action getActionForKeyCode(int keyCode) {
335        ObjectAdapter adapter = getActionsAdapter();
336        if (adapter != null) {
337            for (int i = 0; i < adapter.size(); i++) {
338                Action action = (Action) adapter.get(i);
339                if (action.respondsToKeyCode(keyCode)) {
340                    return action;
341                }
342            }
343        }
344        return null;
345    }
346
347    private void verify() {
348        if (mItem == null) {
349            throw new IllegalArgumentException("Object cannot be null");
350        }
351    }
352}
353