RowPresenter.java revision 9de363b8db05106b03d115c266859fe200d41db7
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.support.v17.leanback.app.HeadersFragment;
17import android.view.View;
18import android.view.ViewGroup;
19
20/**
21 * An abstract {@link Presenter} that renders a {@link Row}.
22 *
23 * <h3>Customize UI widgets</h3>
24 * When a subclass of RowPresenter adds UI widgets, it should subclass
25 * {@link RowPresenter.ViewHolder} and override {@link #createRowViewHolder(ViewGroup)}
26 * and {@link #initializeRowViewHolder(ViewHolder)}. The subclass must use layout id
27 * "row_content" for the widget that will be aligned to the title of any {@link HeadersFragment}
28 * that may exist in the parent fragment. RowPresenter contains an optional and
29 * replaceable {@link RowHeaderPresenter} that renders the header. You can disable
30 * the default rendering or replace the Presenter with a new header presenter
31 * by calling {@link #setHeaderPresenter(RowHeaderPresenter)}.
32 *
33 * <h3>UI events from fragments</h3>
34 * RowPresenter receives calls from its parent (typically a Fragment) when:
35 * <ul>
36 * <li>
37 * A Row is selected via {@link #setRowViewSelected(Presenter.ViewHolder, boolean)}.  The event
38 * is triggered immediately when there is a row selection change before the selection
39 * animation is started.
40 * Subclasses of RowPresenter may override {@link #onRowViewSelected(ViewHolder, boolean)}.
41 * </li>
42 * <li>
43 * A Row is expanded to full width via {@link #setRowViewExpanded(Presenter.ViewHolder, boolean)}.
44 * The event is triggered immediately before the expand animation is started.
45 * Subclasses of RowPresenter may override {@link #onRowViewExpanded(ViewHolder, boolean)}.
46 * </li>
47 * </ul>
48 *
49 * <h3>User events</h3>
50 * RowPresenter provides {@link OnItemSelectedListener} and {@link OnItemClickedListener}.
51 * If a subclass wants to add its own {@link View.OnFocusChangeListener} or
52 * {@link View.OnClickListener}, it must do that in {@link #createRowViewHolder(ViewGroup)}
53 * to be properly chained by the library.  Adding View listeners after
54 * {@link #createRowViewHolder(ViewGroup)} is undefined and may result in
55 * incorrect behavior by the library's listeners.
56 *
57 * <h3>Selection animation</h3>
58 * <p>
59 * When a user scrolls through rows, a fragment will initiate animation and call
60 * {@link #setSelectLevel(Presenter.ViewHolder, float)} with float value between
61 * 0 and 1.  By default, the RowPresenter draws a dim overlay on top of the row
62 * view for views that are not selected. Subclasses may override this default effect
63 * by having {@link #isUsingDefaultSelectEffect()} return false and overriding
64 * {@link #onSelectLevelChanged(ViewHolder)} to apply a different selection effect.
65 * </p>
66 * <p>
67 * Call {@link #setSelectEffectEnabled(boolean)} to enable/disable the select effect,
68 * This will not only enable/disable the default dim effect but also subclasses must
69 * respect this flag as well.
70 * </p>
71 */
72public abstract class RowPresenter extends Presenter {
73
74    static class ContainerViewHolder extends Presenter.ViewHolder {
75        /**
76         * wrapped row view holder
77         */
78        final ViewHolder mRowViewHolder;
79
80        public ContainerViewHolder(RowContainerView containerView, ViewHolder rowViewHolder) {
81            super(containerView);
82            containerView.addRowView(rowViewHolder.view);
83            if (rowViewHolder.mHeaderViewHolder != null) {
84                containerView.addHeaderView(rowViewHolder.mHeaderViewHolder.view);
85            }
86            mRowViewHolder = rowViewHolder;
87            mRowViewHolder.mContainerViewHolder = this;
88        }
89    }
90
91    /**
92     * A view holder for a {@link Row}.
93     */
94    public static class ViewHolder extends Presenter.ViewHolder {
95        ContainerViewHolder mContainerViewHolder;
96        RowHeaderPresenter.ViewHolder mHeaderViewHolder;
97        Row mRow;
98        boolean mSelected;
99        boolean mExpanded;
100        boolean mInitialzed;
101        float mSelectLevel = 0f; // initially unselected
102
103        /**
104         * Constructor for ViewHolder.
105         *
106         * @param view The View bound to the Row.
107         */
108        public ViewHolder(View view) {
109            super(view);
110        }
111
112        /**
113         * Returns the Row bound to the View in this ViewHolder.
114         */
115        public final Row getRow() {
116            return mRow;
117        }
118
119        /**
120         * Returns whether the Row is in its expanded state.
121         *
122         * @return true if the Row is expanded, false otherwise.
123         */
124        public final boolean isExpanded() {
125            return mExpanded;
126        }
127
128        /**
129         * Returns whether the Row is selected.
130         *
131         * @return true if the Row is selected, false otherwise.
132         */
133        public final boolean isSelected() {
134            return mSelected;
135        }
136
137        /**
138         * Returns the current selection level of the Row.
139         */
140        public final float getSelectLevel() {
141            return mSelectLevel;
142        }
143
144        /**
145         * Returns the view holder for the Row header for this Row.
146         */
147        public final RowHeaderPresenter.ViewHolder getHeaderViewHolder() {
148            return mHeaderViewHolder;
149        }
150    }
151
152    private RowHeaderPresenter mHeaderPresenter = new RowHeaderPresenter();
153    private OnItemSelectedListener mOnItemSelectedListener;
154    private OnItemClickedListener mOnItemClickedListener;
155    private OnItemViewSelectedListener mOnItemViewSelectedListener;
156    private OnItemViewClickedListener mOnItemViewClickedListener;
157
158    boolean mSelectEffectEnabled = true;
159
160    @Override
161    public final Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) {
162        ViewHolder vh = createRowViewHolder(parent);
163        vh.mInitialzed = false;
164        Presenter.ViewHolder result;
165        if (needsRowContainerView()) {
166            RowContainerView containerView = new RowContainerView(parent.getContext());
167            if (mHeaderPresenter != null) {
168                vh.mHeaderViewHolder = (RowHeaderPresenter.ViewHolder)
169                        mHeaderPresenter.onCreateViewHolder((ViewGroup) vh.view);
170            }
171            result = new ContainerViewHolder(containerView, vh);
172        } else {
173            result = vh;
174        }
175        initializeRowViewHolder(vh);
176        if (!vh.mInitialzed) {
177            throw new RuntimeException("super.initializeRowViewHolder() must be called");
178        }
179        return result;
180    }
181
182    /**
183     * Called to create a ViewHolder object for a Row. Subclasses will override
184     * this method to return a different concrete ViewHolder object.
185     *
186     * @param parent The parent View for the Row's view holder.
187     * @return A ViewHolder for the Row's View.
188     */
189    protected abstract ViewHolder createRowViewHolder(ViewGroup parent);
190
191    /**
192     * Called after a {@link RowPresenter.ViewHolder} is created for a Row.
193     * Subclasses may override this method and start by calling
194     * super.initializeRowViewHolder(ViewHolder).
195     *
196     * @param vh The ViewHolder to initialize for the Row.
197     */
198    protected void initializeRowViewHolder(ViewHolder vh) {
199        vh.mInitialzed = true;
200    }
201
202    /**
203     * Set the Presenter used for rendering the header. Can be null to disable
204     * header rendering. The method must be called before creating any Row Views.
205     */
206    public final void setHeaderPresenter(RowHeaderPresenter headerPresenter) {
207        mHeaderPresenter = headerPresenter;
208    }
209
210    /**
211     * Get the Presenter used for rendering the header, or null if none has been
212     * set.
213     */
214    public final RowHeaderPresenter getHeaderPresenter() {
215        return mHeaderPresenter;
216    }
217
218    /**
219     * Get the {@link RowPresenter.ViewHolder} from the given Presenter
220     * ViewHolder.
221     */
222    public final ViewHolder getRowViewHolder(Presenter.ViewHolder holder) {
223        if (holder instanceof ContainerViewHolder) {
224            return ((ContainerViewHolder) holder).mRowViewHolder;
225        } else {
226            return (ViewHolder) holder;
227        }
228    }
229
230    /**
231     * Set the expanded state of a Row view.
232     *
233     * @param holder The Row ViewHolder to set expanded state on.
234     * @param expanded True if the Row is expanded, false otherwise.
235     */
236    public final void setRowViewExpanded(Presenter.ViewHolder holder, boolean expanded) {
237        ViewHolder rowViewHolder = getRowViewHolder(holder);
238        rowViewHolder.mExpanded = expanded;
239        onRowViewExpanded(rowViewHolder, expanded);
240    }
241
242    /**
243     * Set the selected state of a Row view.
244     *
245     * @param holder The Row ViewHolder to set expanded state on.
246     * @param selected True if the Row is expanded, false otherwise.
247     */
248    public final void setRowViewSelected(Presenter.ViewHolder holder, boolean selected) {
249        ViewHolder rowViewHolder = getRowViewHolder(holder);
250        rowViewHolder.mSelected = selected;
251        onRowViewSelected(rowViewHolder, selected);
252    }
253
254    /**
255     * Subclass may override this to respond to expanded state changes of a Row.
256     * The default implementation will hide/show the header view. Subclasses may
257     * make visual changes to the Row View but must not create animation on the
258     * Row view.
259     */
260    protected void onRowViewExpanded(ViewHolder vh, boolean expanded) {
261        updateHeaderViewVisibility(vh);
262        vh.view.setActivated(expanded);
263    }
264
265    /**
266     * Subclass may override this to respond to selected state changes of a Row.
267     * Subclass may make visual changes to Row view but must not create
268     * animation on the Row view.
269     */
270    protected void onRowViewSelected(ViewHolder vh, boolean selected) {
271        if (selected) {
272            if (mOnItemViewSelectedListener != null) {
273                mOnItemViewSelectedListener.onItemSelected(null, null, vh, vh.getRow());
274            }
275            if (mOnItemSelectedListener != null) {
276                mOnItemSelectedListener.onItemSelected(null, vh.getRow());
277            }
278        }
279        updateHeaderViewVisibility(vh);
280    }
281
282    private void updateHeaderViewVisibility(ViewHolder vh) {
283        if (mHeaderPresenter != null && vh.mHeaderViewHolder != null) {
284            RowContainerView containerView = ((RowContainerView) vh.mContainerViewHolder.view);
285            containerView.showHeader(vh.isExpanded());
286        }
287    }
288
289    /**
290     * Set the current select level to a value between 0 (unselected) and 1 (selected).
291     * Subclasses may override {@link #onSelectLevelChanged(ViewHolder)} to
292     * respond to changes in the selected level.
293     */
294    public final void setSelectLevel(Presenter.ViewHolder vh, float level) {
295        ViewHolder rowViewHolder = getRowViewHolder(vh);
296        rowViewHolder.mSelectLevel = level;
297        onSelectLevelChanged(rowViewHolder);
298    }
299
300    /**
301     * Get the current select level. The value will be between 0 (unselected)
302     * and 1 (selected).
303     */
304    public final float getSelectLevel(Presenter.ViewHolder vh) {
305        return getRowViewHolder(vh).mSelectLevel;
306    }
307
308    /**
309     * Callback when select level is changed. The default implementation applies
310     * the select level to {@link RowHeaderPresenter#setSelectLevel(RowHeaderPresenter.ViewHolder, float)}
311     * when {@link #getSelectEffectEnabled()} is true. Subclasses may override
312     * this function and implement a different select effect. In this case, you
313     * should also override {@link #isUsingDefaultSelectEffect()} to disable
314     * the default dimming effect applied by the library.
315     */
316    protected void onSelectLevelChanged(ViewHolder vh) {
317        if (getSelectEffectEnabled() && vh.mHeaderViewHolder != null) {
318            mHeaderPresenter.setSelectLevel(vh.mHeaderViewHolder, vh.mSelectLevel);
319        }
320    }
321
322    /**
323     * Enables or disables the row selection effect.
324     * This will not only affect the default dim effect, but subclasses must
325     * respect this flag as well.
326     */
327    public final void setSelectEffectEnabled(boolean applyDimOnSelect) {
328        mSelectEffectEnabled = applyDimOnSelect;
329    }
330
331    /**
332     * Returns true if the row selection effect is enabled.
333     * This value not only determines whether the default dim implementation is
334     * used, but subclasses must also respect this flag.
335     */
336    public final boolean getSelectEffectEnabled() {
337        return mSelectEffectEnabled;
338    }
339
340    /**
341     * Return whether this RowPresenter is using the default dimming effect
342     * provided by the library.  Subclasses may(most likely) return false and
343     * override {@link #onSelectLevelChanged(ViewHolder)}.
344     */
345    public boolean isUsingDefaultSelectEffect() {
346        return true;
347    }
348
349    final boolean needsDefaultSelectEffect() {
350        return isUsingDefaultSelectEffect() && getSelectEffectEnabled();
351    }
352
353    final boolean needsRowContainerView() {
354        return mHeaderPresenter != null;
355    }
356
357    /**
358     * Return true if the Row view can draw outside its bounds.
359     */
360    public boolean canDrawOutOfBounds() {
361        return false;
362    }
363
364    @Override
365    public final void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
366        onBindRowViewHolder(getRowViewHolder(viewHolder), item);
367    }
368
369    protected void onBindRowViewHolder(ViewHolder vh, Object item) {
370        vh.mRow = (Row) item;
371        if (vh.mHeaderViewHolder != null) {
372            mHeaderPresenter.onBindViewHolder(vh.mHeaderViewHolder, item);
373        }
374    }
375
376    @Override
377    public final void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
378        onUnbindRowViewHolder(getRowViewHolder(viewHolder));
379    }
380
381    protected void onUnbindRowViewHolder(ViewHolder vh) {
382        if (vh.mHeaderViewHolder != null) {
383            mHeaderPresenter.onUnbindViewHolder(vh.mHeaderViewHolder);
384        }
385        vh.mRow = null;
386    }
387
388    @Override
389    public final void onViewAttachedToWindow(Presenter.ViewHolder holder) {
390        onRowViewAttachedToWindow(getRowViewHolder(holder));
391    }
392
393    protected void onRowViewAttachedToWindow(ViewHolder vh) {
394        if (vh.mHeaderViewHolder != null) {
395            mHeaderPresenter.onViewAttachedToWindow(vh.mHeaderViewHolder);
396        }
397    }
398
399    @Override
400    public final void onViewDetachedFromWindow(Presenter.ViewHolder holder) {
401        onRowViewDetachedFromWindow(getRowViewHolder(holder));
402    }
403
404    protected void onRowViewDetachedFromWindow(ViewHolder vh) {
405        if (vh.mHeaderViewHolder != null) {
406            mHeaderPresenter.onViewDetachedFromWindow(vh.mHeaderViewHolder);
407        }
408    }
409
410    /**
411     * Set the listener for item or row selection. A RowPresenter fires a row
412     * selection event with a null item. Subclasses (e.g. {@link ListRowPresenter})
413     * can fire a selection event with the selected item.
414     */
415    public final void setOnItemSelectedListener(OnItemSelectedListener listener) {
416        mOnItemSelectedListener = listener;
417    }
418
419    /**
420     * Get the listener for item or row selection.
421     */
422    public final OnItemSelectedListener getOnItemSelectedListener() {
423        return mOnItemSelectedListener;
424    }
425
426    /**
427     * Set the listener for item click events. A RowPresenter does not use this
428     * listener, but a subclass may fire an item click event if it has the concept
429     * of an item. The {@link OnItemClickedListener} will override any
430     * {@link View.OnClickListener} that an item's Presenter sets during
431     * {@link Presenter#onCreateViewHolder(ViewGroup)}. So in general, you
432     * should choose to use an OnItemClickedListener or a {@link
433     * View.OnClickListener}, but not both.
434     */
435    public final void setOnItemClickedListener(OnItemClickedListener listener) {
436        mOnItemClickedListener = listener;
437    }
438
439    /**
440     * Get the listener for item click events.
441     */
442    public final OnItemClickedListener getOnItemClickedListener() {
443        return mOnItemClickedListener;
444    }
445
446    /**
447     * Set listener for item or row selection.  RowPresenter fires row selection
448     * event with null item, subclass of RowPresenter e.g. {@link ListRowPresenter} can
449     * fire a selection event with selected item.
450     */
451    public final void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
452        mOnItemViewSelectedListener = listener;
453    }
454
455    /**
456     * Get listener for item or row selection.
457     */
458    public final OnItemViewSelectedListener getOnItemViewSelectedListener() {
459        return mOnItemViewSelectedListener;
460    }
461
462    /**
463     * Set listener for item click event.  RowPresenter does nothing but subclass of
464     * RowPresenter may fire item click event if it does have a concept of item.
465     * OnItemViewClickedListener will override {@link View.OnClickListener} that
466     * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
467     * So in general,  developer should choose one of the listeners but not both.
468     */
469    public final void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
470        mOnItemViewClickedListener = listener;
471    }
472
473    /**
474     * Set listener for item click event.
475     */
476    public final OnItemViewClickedListener getOnItemViewClickedListener() {
477        return mOnItemViewClickedListener;
478    }
479}
480