DetailsOverviewRow.java revision d805095048f6be52cddbd572ee343c4639ba8187
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 {@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 */
46public 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         */
56        public void onImageDrawableChanged(DetailsOverviewRow row) {
57        }
58
59        /**
60         * Called when DetailsOverviewRow has changed main item.
61         */
62        public void onItemChanged(DetailsOverviewRow row) {
63        }
64
65        /**
66         * Called when DetailsOverviewRow has changed actions adapter.
67         */
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     */
84    public DetailsOverviewRow(Object item) {
85        super(null);
86        mItem = item;
87        verify();
88    }
89
90    /**
91     * Adds listener for the details page.
92     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
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
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
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
311    public final List<Action> getActions() {
312        return getArrayObjectAdapter().unmodifiableList();
313    }
314
315    /**
316     * Returns the {@link ObjectAdapter} for actions.
317     */
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     */
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     */
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
354    private void verify() {
355        if (mItem == null) {
356            throw new IllegalArgumentException("Object cannot be null");
357        }
358    }
359}
360