RowPresenter.java revision cb13a318e577e14461eb008071dddf762847de42
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    }
223
224    /**
225     * Subclass may override and respond to event Row is selected.
226     * Subclass may make visual changes to row view but not allowed to create
227     * animation on the row view.
228     */
229    protected void onRowViewSelected(ViewHolder vh, boolean selected) {
230        if (selected && mOnItemSelectedListener != null) {
231            mOnItemSelectedListener.onItemSelected(null, vh.getRow());
232        }
233    }
234
235    /**
236     * Set current select level from 0(unselected) to 1(selected).
237     * Subclass should override {@link #onSelectLevelChanged(ViewHolder)}.
238     */
239    public final void setSelectLevel(Presenter.ViewHolder vh, float level) {
240        ViewHolder rowViewHolder = getRowViewHolder(vh);
241        rowViewHolder.mSelectLevel = level;
242        onSelectLevelChanged(rowViewHolder);
243    }
244
245    /**
246     * Get current select level from 0(unselected) to 1(selected).
247     */
248    public final float getSelectLevel(Presenter.ViewHolder vh) {
249        return getRowViewHolder(vh).mSelectLevel;
250    }
251
252    /**
253     * Callback when select level is changed.  Default implementation applies select level
254     * to {@link RowHeaderPresenter#setSelectLevel(RowHeaderPresenter.ViewHolder, float)}
255     * when {@link #getSelectEffectEnabled()} is true.
256     * Subclass may override this function and implements its own select effect.  When it
257     * overrides,  it should also override {@link #isUsingDefaultSelectEffect()} to disable
258     * the default dimming effect applied by framework.
259     */
260    protected void onSelectLevelChanged(ViewHolder vh) {
261        if (getSelectEffectEnabled() && vh.mHeaderViewHolder != null) {
262            mHeaderPresenter.setSelectLevel(vh.mHeaderViewHolder, vh.mSelectLevel);
263        }
264    }
265
266    /**
267     * Enables or disables the row selection effect.
268     * This is not only for enable/disable default dim implementation but also subclass must
269     * respect this flag.
270     */
271    public final void setSelectEffectEnabled(boolean applyDimOnSelect) {
272        mSelectEffectEnabled = applyDimOnSelect;
273    }
274
275    /**
276     * Returns true if row selection effect is enabled.
277     * This is not only for enable/disable default dim implementation but also subclass must
278     * respect this flag.
279     */
280    public final boolean getSelectEffectEnabled() {
281        return mSelectEffectEnabled;
282    }
283
284    /**
285     * Return if using default dimming effect provided by framework (fragment).  Subclass
286     * may(most likely) return false and override {@link #onSelectLevelChanged(ViewHolder)}.
287     */
288    public boolean isUsingDefaultSelectEffect() {
289        return true;
290    }
291
292    final boolean needsDefaultSelectEffect() {
293        return isUsingDefaultSelectEffect() && getSelectEffectEnabled();
294    }
295
296    final boolean needsRowContainerView() {
297        return mHeaderPresenter != null;
298    }
299
300    /**
301     * Return true if the Row view can draw outside bounds.
302     */
303    public boolean canDrawOutOfBounds() {
304        return false;
305    }
306
307    @Override
308    public final void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
309        onBindRowViewHolder(getRowViewHolder(viewHolder), item);
310    }
311
312    protected void onBindRowViewHolder(ViewHolder vh, Object item) {
313        vh.mRow = (Row) item;
314        if (vh.mHeaderViewHolder != null) {
315            mHeaderPresenter.onBindViewHolder(vh.mHeaderViewHolder, item);
316        }
317    }
318
319    @Override
320    public final void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
321        onUnbindRowViewHolder(getRowViewHolder(viewHolder));
322    }
323
324    protected void onUnbindRowViewHolder(ViewHolder vh) {
325        if (vh.mHeaderViewHolder != null) {
326            mHeaderPresenter.onUnbindViewHolder(vh.mHeaderViewHolder);
327        }
328        vh.mRow = null;
329    }
330
331    @Override
332    public final void onViewAttachedToWindow(Presenter.ViewHolder holder) {
333        onRowViewAttachedToWindow(getRowViewHolder(holder));
334    }
335
336    protected void onRowViewAttachedToWindow(ViewHolder vh) {
337        if (vh.mHeaderViewHolder != null) {
338            mHeaderPresenter.onViewAttachedToWindow(vh.mHeaderViewHolder);
339        }
340    }
341
342    @Override
343    public final void onViewDetachedFromWindow(Presenter.ViewHolder holder) {
344        onRowViewDetachedFromWindow(getRowViewHolder(holder));
345    }
346
347    protected void onRowViewDetachedFromWindow(ViewHolder vh) {
348        if (vh.mHeaderViewHolder != null) {
349            mHeaderPresenter.onViewDetachedFromWindow(vh.mHeaderViewHolder);
350        }
351    }
352
353    /**
354     * Set listener for item or row selection.  RowPresenter fires row selection
355     * event with null item, subclass of RowPresenter e.g. {@link ListRowPresenter} can
356     * fire a selection event with selected item.
357     */
358    public final void setOnItemSelectedListener(OnItemSelectedListener listener) {
359        mOnItemSelectedListener = listener;
360    }
361
362    /**
363     * Get listener for item or row selection.
364     */
365    public final OnItemSelectedListener getOnItemSelectedListener() {
366        return mOnItemSelectedListener;
367    }
368
369    /**
370     * Set listener for item click event.  RowPresenter does nothing but subclass of
371     * RowPresenter may fire item click event if it does have a concept of item.
372     * OnItemClickedListener will override {@link View.OnClickListener} that
373     * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
374     * So in general,  developer should choose one of the listeners but not both.
375     */
376    public final void setOnItemClickedListener(OnItemClickedListener listener) {
377        mOnItemClickedListener = listener;
378    }
379
380    /**
381     * Set listener for item click event.
382     */
383    public final OnItemClickedListener getOnItemClickedListener() {
384        return mOnItemClickedListener;
385    }
386}
387