ListRowPresenter.java revision b9e89a1544f8cf582f191184fb9b2a4f24e1fa5b
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 java.util.ArrayList;
17
18import android.graphics.Canvas;
19import android.support.v17.leanback.graphics.ColorOverlayDimmer;
20import android.support.v17.leanback.widget.Presenter.ViewHolder;
21import android.support.v7.widget.RecyclerView;
22import android.view.View;
23import android.view.ViewGroup;
24
25/**
26 * ListRowPresenter renders {@link ListRow} using a
27 * {@link HorizontalGridView} hosted in a {@link BrowseRowView}.
28 *
29 * <h3>Hover card</h3>
30 * Optionally, {@link #setHoverCardPresenterSelector(PresenterSelector)} can be used to
31 * display a view for the currently focused list item below the rendered
32 * list. This view is known as a hover card.
33 *
34 * <h3>Selection animation</h3>
35 * ListRowPresenter disables {@link RowPresenter}'s default dimming effect and draw
36 * a dim overlay on top of each individual child items.  Subclass may override and disable
37 * {@link #isUsingDefaultListSelectEffect()} and write its own dim effect in
38 * {@link #onSelectLevelChanged(RowPresenter.ViewHolder)}.
39 */
40public class ListRowPresenter extends RowPresenter {
41
42    private static final String TAG = "ListRowPresenter";
43    private static final boolean DEBUG = false;
44
45    /**
46     * No zoom factor.
47     */
48    public static final int ZOOM_FACTOR_NONE = FocusHighlightHelper.BrowseItemFocusHighlight.ZOOM_FACTOR_NONE;
49
50    /**
51     * A small zoom factor, recommended for large item views.
52     */
53    public static final int ZOOM_FACTOR_SMALL = FocusHighlightHelper.BrowseItemFocusHighlight.ZOOM_FACTOR_SMALL;
54
55    /**
56     * A medium zoom factor, recommended for medium sized item views.
57     */
58    public static final int ZOOM_FACTOR_MEDIUM = FocusHighlightHelper.BrowseItemFocusHighlight.ZOOM_FACTOR_MEDIUM;
59
60    /**
61     * A large zoom factor, recommended for small item views.
62     */
63    public static final int ZOOM_FACTOR_LARGE = FocusHighlightHelper.BrowseItemFocusHighlight.ZOOM_FACTOR_LARGE;
64
65    public static class ViewHolder extends RowPresenter.ViewHolder {
66        final ListRowPresenter mListRowPresenter;
67        final HorizontalGridView mGridView;
68        final ItemBridgeAdapter mItemBridgeAdapter = new ItemBridgeAdapter();
69        final HorizontalHoverCardSwitcher mHoverCardViewSwitcher = new HorizontalHoverCardSwitcher();
70        final ColorOverlayDimmer mColorDimmer;
71
72        public ViewHolder(View rootView, HorizontalGridView gridView, ListRowPresenter p) {
73            super(rootView);
74            mGridView = gridView;
75            mListRowPresenter = p;
76            mColorDimmer = ColorOverlayDimmer.createDefault(rootView.getContext());
77        }
78
79        public final ListRowPresenter getListRowPresenter() {
80            return mListRowPresenter;
81        }
82
83        public final HorizontalGridView getGridView() {
84            return mGridView;
85        }
86    }
87
88    private PresenterSelector mHoverCardPresenterSelector;
89    private int mZoomFactor;
90
91    /**
92     * Constructs a ListRowPresenter with defaults.
93     * Uses {@link #ZOOM_FACTOR_MEDIUM} for focus zooming.
94     */
95    public ListRowPresenter() {
96        this(ZOOM_FACTOR_MEDIUM);
97    }
98
99    /**
100     * Constructs a ListRowPresenter with the given parameters.
101     *
102     * @param zoomFactor Controls the zoom factor used when an item view is focused. One of
103     *         {@link #ZOOM_FACTOR_NONE}, {@link #ZOOM_FACTOR_SMALL}, {@link #ZOOM_FACTOR_MEDIUM},
104     *         {@link #ZOOM_FACTOR_LARGE}
105     */
106    public ListRowPresenter(int zoomFactor) {
107        mZoomFactor = zoomFactor;
108    }
109
110    /**
111     * Returns the zoom factor used for focus highlighting.
112     */
113    public final int getZoomFactor() {
114        return mZoomFactor;
115    }
116
117    @Override
118    protected void initializeRowViewHolder(RowPresenter.ViewHolder holder) {
119        super.initializeRowViewHolder(holder);
120        final ViewHolder rowViewHolder = (ViewHolder) holder;
121        if (needsDefaultListItemDecoration()) {
122            rowViewHolder.mGridView.addItemDecoration(new ItemDecoration(rowViewHolder));
123        }
124        FocusHighlightHelper.setupBrowseItemFocusHighlight(rowViewHolder.mItemBridgeAdapter, mZoomFactor);
125        rowViewHolder.mGridView.setOnChildSelectedListener(
126                new OnChildSelectedListener() {
127            @Override
128            public void onChildSelected(ViewGroup parent, View view, int position, long id) {
129                selectChildView(rowViewHolder, view);
130            }
131        });
132        if (getOnItemClickedListener() != null) {
133            // Only when having an OnItemClickListner, we will attach the OnClickListener.
134            rowViewHolder.mItemBridgeAdapter.setAdapterListener(
135                    new ItemBridgeAdapter.AdapterListener() {
136                @Override
137                public void onCreate(ItemBridgeAdapter.ViewHolder viewHolder) {
138                    viewHolder.mHolder.view.setOnClickListener(new View.OnClickListener() {
139                        @Override
140                        public void onClick(View v) {
141                            ItemBridgeAdapter.ViewHolder ibh = (ItemBridgeAdapter.ViewHolder)
142                                    rowViewHolder.mGridView.getChildViewHolder(v);
143                            if (getOnItemClickedListener() != null) {
144                                getOnItemClickedListener().onItemClicked(ibh.mItem,
145                                        (ListRow) rowViewHolder.mRow);
146                            }
147                        }
148                    });
149                }
150            });
151        }
152    }
153
154    private boolean needsDefaultListItemDecoration() {
155        return isUsingDefaultListSelectEffect() && getSelectEffectEnabled();
156    }
157
158    /**
159     * Set {@link PresenterSelector} used for showing a select object in a hover card.
160     */
161    public final void setHoverCardPresenterSelector(PresenterSelector selector) {
162        mHoverCardPresenterSelector = selector;
163    }
164
165    /**
166     * Get {@link PresenterSelector} used for showing a select object in a hover card.
167     */
168    public final PresenterSelector getHoverCardPresenterSelector() {
169        return mHoverCardPresenterSelector;
170    }
171
172    /*
173     * Perform operations when a child of horizontal grid view is selected.
174     */
175    private void selectChildView(ViewHolder rowViewHolder, View view) {
176        ItemBridgeAdapter.ViewHolder ibh = null;
177        if (view != null) {
178            ibh = (ItemBridgeAdapter.ViewHolder)
179                    rowViewHolder.mGridView.getChildViewHolder(view);
180        }
181        if (view == null) {
182            if (mHoverCardPresenterSelector != null) {
183                rowViewHolder.mHoverCardViewSwitcher.unselect();
184            }
185            if (getOnItemSelectedListener() != null) {
186                getOnItemSelectedListener().onItemSelected(null, rowViewHolder.mRow);
187            }
188        } else if (rowViewHolder.mExpanded && rowViewHolder.mSelected) {
189            if (mHoverCardPresenterSelector != null) {
190                rowViewHolder.mHoverCardViewSwitcher.select(rowViewHolder.mGridView, view,
191                        ibh.mItem);
192            }
193            if (getOnItemSelectedListener() != null) {
194                getOnItemSelectedListener().onItemSelected(ibh.mItem, rowViewHolder.mRow);
195            }
196        }
197    }
198
199    @Override
200    protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) {
201        BrowseRowView rowView = new BrowseRowView(parent.getContext());
202        return new ViewHolder(rowView, rowView.getGridView(), this);
203    }
204
205    @Override
206    protected void onRowViewSelected(RowPresenter.ViewHolder holder, boolean selected) {
207        updateFooterViewSwitcher((ViewHolder) holder);
208        updateInitialChildSelection((ViewHolder) holder);
209    }
210
211    /*
212     * Show or hide hover card when row selection or expanded state is changed.
213     */
214    private void updateFooterViewSwitcher(ViewHolder vh) {
215        if (vh.mExpanded && vh.mSelected) {
216            if (mHoverCardPresenterSelector != null) {
217                vh.mHoverCardViewSwitcher.init((ViewGroup) vh.view,
218                        mHoverCardPresenterSelector);
219            }
220        } else {
221            if (mHoverCardPresenterSelector != null) {
222                vh.mHoverCardViewSwitcher.clear();
223            }
224        }
225    }
226
227    /*
228     * Make initial child selection when row selection state is changed.
229     */
230    private void updateInitialChildSelection(ViewHolder vh) {
231        if (vh.mExpanded && vh.mSelected) {
232            ItemBridgeAdapter.ViewHolder ibh = (ItemBridgeAdapter.ViewHolder)
233                    vh.mGridView.findViewHolderForPosition(
234                            vh.mGridView.getSelectedPosition());
235            selectChildView(vh, ibh == null ? null : ibh.mHolder.view);
236        } else {
237            selectChildView(vh, null);
238        }
239    }
240
241    @Override
242    protected void onRowViewExpanded(RowPresenter.ViewHolder holder, boolean expanded) {
243        super.onRowViewExpanded(holder, expanded);
244        ViewHolder vh = (ViewHolder) holder;
245        vh.mGridView.setClipToPadding(!expanded);
246        vh.mGridView.invalidate();
247        updateFooterViewSwitcher(vh);
248    }
249
250    @Override
251    public void onBindViewHolder(Presenter.ViewHolder holder, Object item) {
252        super.onBindViewHolder(holder, item);
253        ViewHolder vh = (ViewHolder)holder;
254        ListRow rowItem = (ListRow) item;
255        vh.mItemBridgeAdapter.clear();
256        vh.mItemBridgeAdapter.setAdapter(rowItem.getAdapter());
257        vh.mGridView.setAdapter(vh.mItemBridgeAdapter);
258    }
259
260    @Override
261    public void onUnbindViewHolder(Presenter.ViewHolder holder) {
262        ViewHolder vh = (ViewHolder)holder;
263        vh.mGridView.setAdapter(null);
264        super.onUnbindViewHolder(holder);
265    }
266
267    /**
268     * ListRowPresenter overrides the default select effect of {@link RowPresenter}
269     * and return false.
270     */
271    @Override
272    public final boolean isUsingDefaultSelectEffect() {
273        return false;
274    }
275
276    /**
277     * Returns true so that default select effect is applied to each individual
278     * child of {@link HorizontalGridView}.  Subclass may return false to disable
279     * the default implementation.
280     * @see #onSelectLevelChanged(RowPresenter.ViewHolder)
281     */
282    public boolean isUsingDefaultListSelectEffect() {
283        return true;
284    }
285
286    /**
287     * Applies select level to header and draw a default color dim over each child
288     * of {@link HorizontalGridView}.
289     * <p>
290     * Subclass may override this method.  A subclass
291     * needs to call super.onSelectLevelChanged() for applying header select level
292     * and optionally applying a default select level to each child view of
293     * {@link HorizontalGridView} if {@link #isUsingDefaultListSelectEffect()}
294     * is true.  Subclass may override {@link #isUsingDefaultListSelectEffect()} to return
295     * false and deal with the individual item select level by itself.
296     * </p>
297     */
298    @Override
299    protected void onSelectLevelChanged(RowPresenter.ViewHolder holder) {
300        super.onSelectLevelChanged(holder);
301        if (needsDefaultListItemDecoration()) {
302            ViewHolder vh = (ViewHolder) holder;
303            vh.mColorDimmer.setActiveLevel(holder.mSelectLevel);
304            vh.mGridView.invalidate();
305        }
306    }
307
308    private void drawDimSelectionForChildren(ViewHolder vh, Canvas c) {
309        final ColorOverlayDimmer dimmer = vh.mColorDimmer;
310        if (dimmer.needsDraw()) {
311            final HorizontalGridView gridView = vh.mGridView;
312            // Clip to padding when not expanded
313            if (!vh.mExpanded) {
314                c.clipRect(gridView.getPaddingLeft(), gridView.getPaddingTop(),
315                        gridView.getWidth() - gridView.getPaddingRight(),
316                        gridView.getHeight() - gridView.getPaddingBottom());
317            }
318            for (int i = 0, count = gridView.getChildCount(); i < count; i++) {
319                dimmer.drawColorOverlay(c, gridView.getChildAt(i), true);
320            }
321        }
322    }
323
324    final class ItemDecoration extends RecyclerView.ItemDecoration {
325        ViewHolder mViewHolder;
326        ItemDecoration(ViewHolder viewHolder) {
327            mViewHolder = viewHolder;
328        }
329        @Override
330        public void onDrawOver(Canvas c, RecyclerView parent) {
331            drawDimSelectionForChildren(mViewHolder, c);
332        }
333    }
334
335}
336