RowPresenter.java revision 4df06cbe8f6dd087fc8f1068faa77923cb297365
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 * A presenter that renders {@link Row}.
22 *
23 * <h3>Customize UI widgets</h3>
24 * When subclass of RowPresenter adds UI widgets,  it should subclass
25 * {@link RowPresenter.ViewHolder} and override {@link #createRowViewHolder(ViewGroup)}
26 * and {@link #initializeRowViewHolder(ViewHolder)}.  Subclass must use layout id
27 * "row_content" for the widget that will be aligned to title of {@link HeadersFragment}.
28 * RowPresenter contains an optional and replaceable {@link RowHeaderPresenter} that
29 * renders header.  User can disable default rendering or replace with a new header presenter
30 * by calling {@link #setHeaderPresenter(RowHeaderPresenter)}.
31 *
32 * <h3>UI events from fragments</h3>
33 * In addition to {@link Presenter} which defines how to render and bind data to row view,
34 * RowPresenter receives calls from upper level(typically a fragment) when:
35 * <ul>
36 * <li>
37 * 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 * Subclass of RowPresenter may override and add more works in
41 * {@link #onRowViewSelected(ViewHolder, boolean)}.
42 * </li>
43 * <li>
44 * Row is expanded to full width via {@link #setRowViewExpanded(Presenter.ViewHolder, boolean)}.
45 * The event is triggered immediately before the expand animation is started.
46 * Subclass of RowPresenter may override and add more works in
47 * {@link #onRowViewExpanded(ViewHolder, boolean)}.
48 * </li>
49 * </ul>
50 *
51 * <h3>User events:</h3>
52 * RowPresenter provides {@link OnItemSelectedListener} and {@link OnItemClickedListener}.
53 * If subclass wants to add its own {@link View.OnFocusChangeListener} or
54 * {@link View.OnClickListener}, it must do that in {@link #createRowViewHolder(ViewGroup)}
55 * to be properly chained by framework.  Adding view listeners after
56 * {@link #createRowViewHolder(ViewGroup)} will interfere framework's listeners.
57 *
58 * <h3>Selection animation</h3>
59 * <p>
60 * When user scrolls through rows,  fragment will initiate animation and call
61 * {@link #setSelectLevel(Presenter.ViewHolder, float)} with float value 0~1.  By default, fragment
62 * draws a dim overlay on top of row view for views not selected.  Subclass may override
63 * this default effect by having {@link #isUsingDefaultSelectEffect()} return false
64 * and override {@link #onSelectLevelChanged(ViewHolder)} to apply its own selection effect.
65 * </p>
66 * <p>
67 * Call {@link #setSelectEffectEnabled(boolean)} to enable/disable select effect,
68 * This is not only for enable/disable default dim implementation but also subclass must
69 * respect this flag.
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    public static class ViewHolder extends Presenter.ViewHolder {
92        ContainerViewHolder mContainerViewHolder;
93        RowHeaderPresenter.ViewHolder mHeaderViewHolder;
94        Row mRow;
95        boolean mSelected;
96        boolean mExpanded;
97        boolean mInitialzed;
98        float mSelectLevel = 0f; // initially unselected
99        public ViewHolder(View view) {
100            super(view);
101        }
102        public final Row getRow() {
103            return mRow;
104        }
105        public final boolean isExpanded() {
106            return mExpanded;
107        }
108        public final boolean isSelected() {
109            return mSelected;
110        }
111        public final float getSelectLevel() {
112            return mSelectLevel;
113        }
114        public final RowHeaderPresenter.ViewHolder getHeaderViewHolder() {
115            return mHeaderViewHolder;
116        }
117    }
118
119    private RowHeaderPresenter mHeaderPresenter = new RowHeaderPresenter();
120    private OnItemSelectedListener mOnItemSelectedListener;
121    private OnItemClickedListener mOnItemClickedListener;
122
123    boolean mSelectEffectEnabled = true;
124
125    @Override
126    public final Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) {
127        ViewHolder vh = createRowViewHolder(parent);
128        vh.mInitialzed = false;
129        Presenter.ViewHolder result;
130        if (needsRowContainerView()) {
131            RowContainerView containerView = new RowContainerView(parent.getContext());
132            if (mHeaderPresenter != null) {
133                vh.mHeaderViewHolder = (RowHeaderPresenter.ViewHolder)
134                        mHeaderPresenter.onCreateViewHolder((ViewGroup) vh.view);
135            }
136            result = new ContainerViewHolder(containerView, vh);
137        } else {
138            result = vh;
139        }
140        initializeRowViewHolder(vh);
141        if (!vh.mInitialzed) {
142            throw new RuntimeException("super.initializeRowViewHolder() must be called");
143        }
144        return result;
145    }
146
147    /**
148     * Called to create a ViewHolder object for row,  subclass of {@link RowPresenter}
149     * should override and return a different concrete ViewHolder object.
150     */
151    protected abstract ViewHolder createRowViewHolder(ViewGroup parent);
152
153    /**
154     * Called after a {@link RowPresenter.ViewHolder} is created,
155     * subclass of {@link RowPresenter} may override this method and start with calling
156     * super.initializeRowViewHolder(ViewHolder).
157     */
158    protected void initializeRowViewHolder(ViewHolder vh) {
159        vh.mInitialzed = true;
160    }
161
162    /**
163     * Change the presenter used for rendering header. Can be null to disable header rendering.
164     * The method must be called before creating any row view.
165     */
166    public final void setHeaderPresenter(RowHeaderPresenter headerPresenter) {
167        mHeaderPresenter = headerPresenter;
168    }
169
170    /**
171     * Get optional presenter used for rendering header.  May return null.
172     */
173    public final RowHeaderPresenter getHeaderPresenter() {
174        return mHeaderPresenter;
175    }
176
177    /**
178     * Get wrapped {@link RowPresenter.ViewHolder}
179     */
180    protected final ViewHolder getRowViewHolder(Presenter.ViewHolder holder) {
181        if (holder instanceof ContainerViewHolder) {
182            return ((ContainerViewHolder) holder).mRowViewHolder;
183        } else {
184            return (ViewHolder) holder;
185        }
186    }
187
188    /**
189     * Change expanded state of row view.
190     */
191    public final void setRowViewExpanded(Presenter.ViewHolder holder, boolean expanded) {
192        ViewHolder rowViewHolder = getRowViewHolder(holder);
193        rowViewHolder.mExpanded = expanded;
194        onRowViewExpanded(rowViewHolder, expanded);
195    }
196
197    /**
198     * Change select state of row view.
199     */
200    public final void setRowViewSelected(Presenter.ViewHolder holder, boolean selected) {
201        ViewHolder rowViewHolder = getRowViewHolder(holder);
202        rowViewHolder.mSelected = selected;
203        onRowViewSelected(rowViewHolder, selected);
204    }
205
206    /**
207     * Subclass may override and respond to expanded state change of row in fragment.
208     * Default implementation hide/show header view depending on expanded state.
209     * Subclass may make visual changes to row view but not allowed to create
210     * animation on the row view.
211     */
212    protected void onRowViewExpanded(ViewHolder vh, boolean expanded) {
213        if (mHeaderPresenter != null && vh.mHeaderViewHolder != null) {
214            RowContainerView containerView = ((RowContainerView) vh.mContainerViewHolder.view);
215            View headerView = vh.mHeaderViewHolder.view;
216            if (expanded) {
217                containerView.addHeaderView(headerView);
218            } else {
219                containerView.removeHeaderView(headerView);
220            }
221        }
222        vh.view.setActivated(expanded);
223    }
224
225    /**
226     * Subclass may override and respond to event Row is selected.
227     * Subclass may make visual changes to row view but not allowed to create
228     * animation on the row view.
229     */
230    protected void onRowViewSelected(ViewHolder vh, boolean selected) {
231        if (selected && mOnItemSelectedListener != null) {
232            mOnItemSelectedListener.onItemSelected(null, vh.getRow());
233        }
234    }
235
236    /**
237     * Set current select level from 0(unselected) to 1(selected).
238     * Subclass should override {@link #onSelectLevelChanged(ViewHolder)}.
239     */
240    public final void setSelectLevel(Presenter.ViewHolder vh, float level) {
241        ViewHolder rowViewHolder = getRowViewHolder(vh);
242        rowViewHolder.mSelectLevel = level;
243        onSelectLevelChanged(rowViewHolder);
244    }
245
246    /**
247     * Get current select level from 0(unselected) to 1(selected).
248     */
249    public final float getSelectLevel(Presenter.ViewHolder vh) {
250        return getRowViewHolder(vh).mSelectLevel;
251    }
252
253    /**
254     * Callback when select level is changed.  Default implementation applies select level
255     * to {@link RowHeaderPresenter#setSelectLevel(RowHeaderPresenter.ViewHolder, float)}
256     * when {@link #getSelectEffectEnabled()} is true.
257     * Subclass may override this function and implements its own select effect.  When it
258     * overrides,  it should also override {@link #isUsingDefaultSelectEffect()} to disable
259     * the default dimming effect applied by framework.
260     */
261    protected void onSelectLevelChanged(ViewHolder vh) {
262        if (getSelectEffectEnabled() && vh.mHeaderViewHolder != null) {
263            mHeaderPresenter.setSelectLevel(vh.mHeaderViewHolder, vh.mSelectLevel);
264        }
265    }
266
267    /**
268     * Enables or disables the row selection effect.
269     * This is not only for enable/disable default dim implementation but also subclass must
270     * respect this flag.
271     */
272    public final void setSelectEffectEnabled(boolean applyDimOnSelect) {
273        mSelectEffectEnabled = applyDimOnSelect;
274    }
275
276    /**
277     * Returns true if row selection effect is enabled.
278     * This is not only for enable/disable default dim implementation but also subclass must
279     * respect this flag.
280     */
281    public final boolean getSelectEffectEnabled() {
282        return mSelectEffectEnabled;
283    }
284
285    /**
286     * Return if using default dimming effect provided by framework (fragment).  Subclass
287     * may(most likely) return false and override {@link #onSelectLevelChanged(ViewHolder)}.
288     */
289    public boolean isUsingDefaultSelectEffect() {
290        return true;
291    }
292
293    final boolean needsDefaultSelectEffect() {
294        return isUsingDefaultSelectEffect() && getSelectEffectEnabled();
295    }
296
297    final boolean needsRowContainerView() {
298        return mHeaderPresenter != null;
299    }
300
301    /**
302     * Return true if the Row view can draw outside bounds.
303     */
304    public boolean canDrawOutOfBounds() {
305        return false;
306    }
307
308    @Override
309    public final void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
310        onBindRowViewHolder(getRowViewHolder(viewHolder), item);
311    }
312
313    protected void onBindRowViewHolder(ViewHolder vh, Object item) {
314        vh.mRow = (Row) item;
315        if (vh.mHeaderViewHolder != null) {
316            mHeaderPresenter.onBindViewHolder(vh.mHeaderViewHolder, item);
317        }
318    }
319
320    @Override
321    public final void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
322        onUnbindRowViewHolder(getRowViewHolder(viewHolder));
323    }
324
325    protected void onUnbindRowViewHolder(ViewHolder vh) {
326        if (vh.mHeaderViewHolder != null) {
327            mHeaderPresenter.onUnbindViewHolder(vh.mHeaderViewHolder);
328        }
329        vh.mRow = null;
330    }
331
332    @Override
333    public final void onViewAttachedToWindow(Presenter.ViewHolder holder) {
334        onRowViewAttachedToWindow(getRowViewHolder(holder));
335    }
336
337    protected void onRowViewAttachedToWindow(ViewHolder vh) {
338        if (vh.mHeaderViewHolder != null) {
339            mHeaderPresenter.onViewAttachedToWindow(vh.mHeaderViewHolder);
340        }
341    }
342
343    @Override
344    public final void onViewDetachedFromWindow(Presenter.ViewHolder holder) {
345        onRowViewDetachedFromWindow(getRowViewHolder(holder));
346    }
347
348    protected void onRowViewDetachedFromWindow(ViewHolder vh) {
349        if (vh.mHeaderViewHolder != null) {
350            mHeaderPresenter.onViewDetachedFromWindow(vh.mHeaderViewHolder);
351        }
352    }
353
354    /**
355     * Set listener for item or row selection.  RowPresenter fires row selection
356     * event with null item, subclass of RowPresenter e.g. {@link ListRowPresenter} can
357     * fire a selection event with selected item.
358     */
359    public final void setOnItemSelectedListener(OnItemSelectedListener listener) {
360        mOnItemSelectedListener = listener;
361    }
362
363    /**
364     * Get listener for item or row selection.
365     */
366    public final OnItemSelectedListener getOnItemSelectedListener() {
367        return mOnItemSelectedListener;
368    }
369
370    /**
371     * Set listener for item click event.  RowPresenter does nothing but subclass of
372     * RowPresenter may fire item click event if it does have a concept of item.
373     * OnItemClickedListener will override {@link View.OnClickListener} that
374     * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
375     * So in general,  developer should choose one of the listeners but not both.
376     */
377    public final void setOnItemClickedListener(OnItemClickedListener listener) {
378        mOnItemClickedListener = listener;
379    }
380
381    /**
382     * Set listener for item click event.
383     */
384    public final OnItemClickedListener getOnItemClickedListener() {
385        return mOnItemClickedListener;
386    }
387}
388