RowPresenter.java revision 0946602a3f3815a5f7d46dfc571b3c60483f1ea4
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(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(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(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    public static class ViewHolder extends Presenter.ViewHolder {
75        RowHeaderPresenter.ViewHolder mHeaderViewHolder;
76        Row mRow;
77        boolean mSelected;
78        boolean mExpanded;
79        boolean mInitialzed;
80        float mSelectLevel = 0f; // initially unselected
81        public ViewHolder(View view) {
82            super(view);
83        }
84        public final Row getRow() {
85            return mRow;
86        }
87        public final boolean isExpanded() {
88            return mExpanded;
89        }
90        public final boolean isSelected() {
91            return mSelected;
92        }
93        public final float getSelectLevel() {
94            return mSelectLevel;
95        }
96        public final RowHeaderPresenter.ViewHolder getHeaderViewHolder() {
97            return mHeaderViewHolder;
98        }
99    }
100
101    private RowHeaderPresenter mHeaderPresenter = new RowHeaderPresenter();
102    private OnItemSelectedListener mOnItemSelectedListener;
103    private OnItemClickedListener mOnItemClickedListener;
104
105    boolean mSelectEffectEnabled = true;
106
107    @Override
108    public final Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) {
109        ViewHolder vh = createRowViewHolder(parent);
110        vh.mInitialzed = false;
111        initializeRowViewHolder(vh);
112        if (!vh.mInitialzed) {
113            throw new RuntimeException("super.initializeRowViewHolder() must be called");
114        }
115        return vh;
116    }
117
118    /**
119     * Called to create a ViewHolder object for row,  subclass of {@link RowPresenter}
120     * should override and return a different concrete ViewHolder object.
121     */
122    protected abstract ViewHolder createRowViewHolder(ViewGroup parent);
123
124    /**
125     * Called after a {@link RowPresenter.ViewHolder} is created,
126     * subclass of {@link RowPresenter} may override this method and start with calling
127     * super.initializeRowViewHolder(ViewHolder).
128     */
129    protected void initializeRowViewHolder(ViewHolder vh) {
130        if (mHeaderPresenter != null) {
131            vh.mHeaderViewHolder = (RowHeaderPresenter.ViewHolder)
132                    mHeaderPresenter.onCreateViewHolder((ViewGroup) vh.view);
133            ((ViewGroup) vh.view).addView(vh.mHeaderViewHolder.view, 0);
134        }
135        vh.mInitialzed = true;
136    }
137
138    /**
139     * Change the presenter used for rendering header. Can be null to disable header rendering.
140     * The method must be called before creating any row view.
141     */
142    public final void setHeaderPresenter(RowHeaderPresenter headerPresenter) {
143        mHeaderPresenter = headerPresenter;
144    }
145
146    /**
147     * Get optional presenter used for rendering header.  May return null.
148     */
149    public final RowHeaderPresenter getHeaderPresenter() {
150        return mHeaderPresenter;
151    }
152
153    /**
154     * Change expanded state of row view.
155     */
156    public final void setRowViewExpanded(ViewHolder holder, boolean expanded) {
157        holder.mExpanded = expanded;
158        onRowViewExpanded(holder, expanded);
159    }
160
161    /**
162     * Change select state of row view.
163     */
164    public final void setRowViewSelected(ViewHolder holder, boolean selected) {
165        holder.mSelected = selected;
166        onRowViewSelected(holder, selected);
167    }
168
169    /**
170     * Subclass may override and respond to expanded state change of row in fragment.
171     * Default implementation hide/show header view depending on expanded state.
172     * Subclass may make visual changes to row view but not allowed to create
173     * animation on the row view.
174     */
175    protected void onRowViewExpanded(ViewHolder vh, boolean expanded) {
176        if (mHeaderPresenter != null && vh.mHeaderViewHolder != null) {
177            mHeaderPresenter.setHidden(vh.mHeaderViewHolder, !expanded);
178        }
179    }
180
181    /**
182     * Subclass may override and respond to event Row is selected.
183     * Subclass may make visual changes to row view but not allowed to create
184     * animation on the row view.
185     */
186    protected void onRowViewSelected(ViewHolder vh, boolean selected) {
187        if (selected && mOnItemSelectedListener != null) {
188            mOnItemSelectedListener.onItemSelected(null, vh.getRow());
189        }
190    }
191
192    /**
193     * Set current select level from 0(unselected) to 1(selected).
194     * Subclass should override {@link #onSelectLevelChanged(ViewHolder)}.
195     */
196    public final void setSelectLevel(ViewHolder vh, float level) {
197        vh.mSelectLevel = level;
198        onSelectLevelChanged(vh);
199    }
200
201    /**
202     * Get current select level from 0(unselected) to 1(selected).
203     */
204    public final float getSelectLevel(ViewHolder vh) {
205        return vh.mSelectLevel;
206    }
207
208    /**
209     * Callback when select level is changed.  Default implementation applies select level
210     * to {@link RowHeaderPresenter#setSelectLevel(RowHeaderPresenter.ViewHolder, float)}
211     * when {@link #getSelectEffectEnabled()} is true.
212     * Subclass may override this function and implements its own select effect.  When it
213     * overrides,  it should also override {@link #isUsingDefaultSelectEffect()} to disable
214     * the default dimming effect applied by framework.
215     */
216    protected void onSelectLevelChanged(ViewHolder vh) {
217        if (getSelectEffectEnabled() && vh.mHeaderViewHolder != null) {
218            mHeaderPresenter.setSelectLevel(vh.mHeaderViewHolder, vh.mSelectLevel);
219        }
220    }
221
222    /**
223     * Enables or disables the row selection effect.
224     * This is not only for enable/disable default dim implementation but also subclass must
225     * respect this flag.
226     */
227    public final void setSelectEffectEnabled(boolean applyDimOnSelect) {
228        mSelectEffectEnabled = applyDimOnSelect;
229    }
230
231    /**
232     * Returns true if row selection effect is enabled.
233     * This is not only for enable/disable default dim implementation but also subclass must
234     * respect this flag.
235     */
236    public final boolean getSelectEffectEnabled() {
237        return mSelectEffectEnabled;
238    }
239
240    /**
241     * Return if using default dimming effect provided by framework (fragment).  Subclass
242     * may(most likely) return false and override {@link #onSelectLevelChanged(ViewHolder)}.
243     */
244    public boolean isUsingDefaultSelectEffect() {
245        return true;
246    }
247
248    /**
249     * Return true if the Row view can draw outside bounds.
250     */
251    public boolean canDrawOutOfBounds() {
252        return false;
253    }
254
255    @Override
256    public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
257        ViewHolder vh = (ViewHolder) viewHolder;
258        vh.mRow = (Row) item;
259        if (vh.mHeaderViewHolder != null) {
260            mHeaderPresenter.onBindViewHolder(vh.mHeaderViewHolder, item);
261        }
262    }
263
264    @Override
265    public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
266        ViewHolder vh = (ViewHolder) viewHolder;
267        if (vh.mHeaderViewHolder != null) {
268            mHeaderPresenter.onUnbindViewHolder(vh.mHeaderViewHolder);
269        }
270        vh.mRow = null;
271    }
272
273    @Override
274    public void onViewAttachedToWindow(Presenter.ViewHolder holder) {
275        ViewHolder vh = (ViewHolder) holder;
276        if (vh.mHeaderViewHolder != null) {
277            mHeaderPresenter.onViewAttachedToWindow(vh.mHeaderViewHolder);
278        }
279    }
280
281    @Override
282    public void onViewDetachedFromWindow(Presenter.ViewHolder holder) {
283        ViewHolder vh = (ViewHolder) holder;
284        if (vh.mHeaderViewHolder != null) {
285            mHeaderPresenter.onViewDetachedFromWindow(vh.mHeaderViewHolder);
286        }
287    }
288
289    /**
290     * Set listener for item or row selection.  RowPresenter fires row selection
291     * event with null item, subclass of RowPresenter e.g. {@link ListRowPresenter} can
292     * fire a selection event with selected item.
293     */
294    public final void setOnItemSelectedListener(OnItemSelectedListener listener) {
295        mOnItemSelectedListener = listener;
296    }
297
298    /**
299     * Get listener for item or row selection.
300     */
301    public final OnItemSelectedListener getOnItemSelectedListener() {
302        return mOnItemSelectedListener;
303    }
304
305    /**
306     * Set listener for item click event.  RowPresenter does nothing but subclass of
307     * RowPresenter may fire item click event if it does have a concept of item.
308     * OnItemClickedListener will override {@link View.OnClickListener} that
309     * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
310     * So in general,  developer should choose one of the listeners but not both.
311     */
312    public final void setOnItemClickedListener(OnItemClickedListener listener) {
313        mOnItemClickedListener = listener;
314    }
315
316    /**
317     * Set listener for item click event.
318     */
319    public final OnItemClickedListener getOnItemClickedListener() {
320        return mOnItemClickedListener;
321    }
322}
323