ListRowPresenter.java revision db1e9bb04638eb6b0b16e849e433d1c3b6f4296c
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    public static class ViewHolder extends RowPresenter.ViewHolder {
46        final ListRowPresenter mListRowPresenter;
47        final HorizontalGridView mGridView;
48        final ItemBridgeAdapter mItemBridgeAdapter = new ItemBridgeAdapter();
49        final HorizontalHoverCardSwitcher mHoverCardViewSwitcher = new HorizontalHoverCardSwitcher();
50        final ColorOverlayDimmer mColorDimmer;
51
52        public ViewHolder(View rootView, HorizontalGridView gridView, ListRowPresenter p) {
53            super(rootView);
54            mGridView = gridView;
55            mListRowPresenter = p;
56            mColorDimmer = ColorOverlayDimmer.createDefault(rootView.getContext());
57        }
58
59        public final ListRowPresenter getListRowPresenter() {
60            return mListRowPresenter;
61        }
62
63        public final HorizontalGridView getGridView() {
64            return mGridView;
65        }
66    }
67
68    private PresenterSelector mHoverCardPresenterSelector;
69
70    @Override
71    protected void initializeRowViewHolder(RowPresenter.ViewHolder holder) {
72        super.initializeRowViewHolder(holder);
73        final ViewHolder rowViewHolder = (ViewHolder) holder;
74        if (needsDefaultListItemDecoration()) {
75            rowViewHolder.mGridView.addItemDecoration(new ItemDecoration(rowViewHolder));
76        }
77        FocusHighlightHelper.setupBrowseItemFocusHighlight(rowViewHolder.mItemBridgeAdapter);
78        rowViewHolder.mGridView.setOnChildSelectedListener(
79                new OnChildSelectedListener() {
80            @Override
81            public void onChildSelected(ViewGroup parent, View view, int position, long id) {
82                selectChildView(rowViewHolder, view);
83            }
84        });
85        if (getOnItemClickedListener() != null) {
86            // Only when having an OnItemClickListner, we will attach the OnClickListener.
87            rowViewHolder.mItemBridgeAdapter.setAdapterListener(
88                    new ItemBridgeAdapter.AdapterListener() {
89                @Override
90                public void onCreate(ItemBridgeAdapter.ViewHolder viewHolder) {
91                    viewHolder.mHolder.view.setOnClickListener(new View.OnClickListener() {
92                        @Override
93                        public void onClick(View v) {
94                            ItemBridgeAdapter.ViewHolder ibh = (ItemBridgeAdapter.ViewHolder)
95                                    rowViewHolder.mGridView.getChildViewHolder(v);
96                            if (getOnItemClickedListener() != null) {
97                                getOnItemClickedListener().onItemClicked(ibh.mItem,
98                                        (ListRow) rowViewHolder.mRow);
99                            }
100                        }
101                    });
102                }
103            });
104        }
105    }
106
107    private boolean needsDefaultListItemDecoration() {
108        return isUsingDefaultListSelectEffect() && getSelectEffectEnabled();
109    }
110
111    /**
112     * Set {@link PresenterSelector} used for showing a select object in a hover card.
113     */
114    public final void setHoverCardPresenterSelector(PresenterSelector selector) {
115        mHoverCardPresenterSelector = selector;
116    }
117
118    /**
119     * Get {@link PresenterSelector} used for showing a select object in a hover card.
120     */
121    public final PresenterSelector getHoverCardPresenterSelector() {
122        return mHoverCardPresenterSelector;
123    }
124
125    /*
126     * Perform operations when a child of horizontal grid view is selected.
127     */
128    private void selectChildView(ViewHolder rowViewHolder, View view) {
129        ItemBridgeAdapter.ViewHolder ibh = null;
130        if (view != null) {
131            ibh = (ItemBridgeAdapter.ViewHolder)
132                    rowViewHolder.mGridView.getChildViewHolder(view);
133        }
134        if (view == null) {
135            if (mHoverCardPresenterSelector != null) {
136                rowViewHolder.mHoverCardViewSwitcher.unselect();
137            }
138            if (getOnItemSelectedListener() != null) {
139                getOnItemSelectedListener().onItemSelected(null, rowViewHolder.mRow);
140            }
141        } else if (rowViewHolder.mExpanded && rowViewHolder.mSelected) {
142            if (mHoverCardPresenterSelector != null) {
143                rowViewHolder.mHoverCardViewSwitcher.select(rowViewHolder.mGridView, view,
144                        ibh.mItem);
145            }
146            if (getOnItemSelectedListener() != null) {
147                getOnItemSelectedListener().onItemSelected(ibh.mItem, rowViewHolder.mRow);
148            }
149        }
150    }
151
152    @Override
153    protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) {
154        BrowseRowView rowView = new BrowseRowView(parent.getContext());
155        return new ViewHolder(rowView, rowView.getGridView(), this);
156    }
157
158    @Override
159    protected void onRowViewSelected(RowPresenter.ViewHolder holder, boolean selected) {
160        updateFooterViewSwitcher((ViewHolder) holder);
161        updateInitialChildSelection((ViewHolder) holder);
162    }
163
164    /*
165     * Show or hide hover card when row selection or expanded state is changed.
166     */
167    private void updateFooterViewSwitcher(ViewHolder vh) {
168        if (vh.mExpanded && vh.mSelected) {
169            if (mHoverCardPresenterSelector != null) {
170                vh.mHoverCardViewSwitcher.init((ViewGroup) vh.view,
171                        mHoverCardPresenterSelector);
172            }
173        } else {
174            if (mHoverCardPresenterSelector != null) {
175                vh.mHoverCardViewSwitcher.clear();
176            }
177        }
178    }
179
180    /*
181     * Make initial child selection when row selection state is changed.
182     */
183    private void updateInitialChildSelection(ViewHolder vh) {
184        if (vh.mExpanded && vh.mSelected) {
185            ItemBridgeAdapter.ViewHolder ibh = (ItemBridgeAdapter.ViewHolder)
186                    vh.mGridView.findViewHolderForPosition(
187                            vh.mGridView.getSelectedPosition());
188            selectChildView(vh, ibh == null ? null : ibh.mHolder.view);
189        } else {
190            selectChildView(vh, null);
191        }
192    }
193
194    @Override
195    protected void onRowViewExpanded(RowPresenter.ViewHolder holder, boolean expanded) {
196        super.onRowViewExpanded(holder, expanded);
197        ViewHolder vh = (ViewHolder) holder;
198        vh.mGridView.setClipToPadding(!expanded);
199        vh.mGridView.invalidate();
200        updateFooterViewSwitcher(vh);
201    }
202
203    @Override
204    public void onBindViewHolder(Presenter.ViewHolder holder, Object item) {
205        super.onBindViewHolder(holder, item);
206        ViewHolder vh = (ViewHolder)holder;
207        ListRow rowItem = (ListRow) item;
208        vh.mItemBridgeAdapter.clear();
209        vh.mItemBridgeAdapter.setAdapter(rowItem.getAdapter());
210        vh.mGridView.setAdapter(vh.mItemBridgeAdapter);
211    }
212
213    @Override
214    public void onUnbindViewHolder(Presenter.ViewHolder holder) {
215        ViewHolder vh = (ViewHolder)holder;
216        vh.mGridView.setAdapter(null);
217        super.onUnbindViewHolder(holder);
218    }
219
220    /**
221     * ListRowPresenter overrides the default select effect of {@link RowPresenter}
222     * and return false.
223     */
224    @Override
225    public final boolean isUsingDefaultSelectEffect() {
226        return false;
227    }
228
229    /**
230     * Returns true so that default select effect is applied to each individual
231     * child of {@link HorizontalGridView}.  Subclass may return false to disable
232     * the default implementation.
233     * @see #onSelectLevelChanged(RowPresenter.ViewHolder)
234     */
235    public boolean isUsingDefaultListSelectEffect() {
236        return true;
237    }
238
239    /**
240     * Applies select level to header and draw a default color dim over each child
241     * of {@link HorizontalGridView}.
242     * <p>
243     * Subclass may override this method.  A subclass
244     * needs to call super.onSelectLevelChanged() for applying header select level
245     * and optionally applying a default select level to each child view of
246     * {@link HorizontalGridView} if {@link #isUsingDefaultListSelectEffect()}
247     * is true.  Subclass may override {@link #isUsingDefaultListSelectEffect()} to return
248     * false and deal with the individual item select level by itself.
249     * </p>
250     */
251    @Override
252    protected void onSelectLevelChanged(RowPresenter.ViewHolder holder) {
253        super.onSelectLevelChanged(holder);
254        if (needsDefaultListItemDecoration()) {
255            ViewHolder vh = (ViewHolder) holder;
256            vh.mColorDimmer.setActiveLevel(holder.mSelectLevel);
257            vh.mGridView.invalidate();
258        }
259    }
260
261    private void drawDimSelectionForChildren(ViewHolder vh, Canvas c) {
262        final ColorOverlayDimmer dimmer = vh.mColorDimmer;
263        if (dimmer.needsDraw()) {
264            final HorizontalGridView gridView = vh.mGridView;
265            // Clip to padding when not expanded
266            if (!vh.mExpanded) {
267                c.clipRect(gridView.getPaddingLeft(), gridView.getPaddingTop(),
268                        gridView.getWidth() - gridView.getPaddingRight(),
269                        gridView.getHeight() - gridView.getPaddingBottom());
270            }
271            for (int i = 0, count = gridView.getChildCount(); i < count; i++) {
272                dimmer.drawColorOverlay(c, gridView.getChildAt(i), true);
273            }
274        }
275    }
276
277    final class ItemDecoration extends RecyclerView.ItemDecoration {
278        ViewHolder mViewHolder;
279        ItemDecoration(ViewHolder viewHolder) {
280            mViewHolder = viewHolder;
281        }
282        @Override
283        public void onDrawOver(Canvas c, RecyclerView parent) {
284            drawDimSelectionForChildren(mViewHolder, c);
285        }
286    }
287
288}
289