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