ListRowPresenter.java revision 254b417129de2a8c5612826a152f8a26c8f1d0e8
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.content.Context;
17import android.content.res.TypedArray;
18import android.os.Build;
19import android.support.v17.leanback.R;
20import android.support.v17.leanback.system.Settings;
21import android.util.Log;
22import android.view.KeyEvent;
23import android.view.View;
24import android.view.ViewGroup;
25import android.view.ViewGroup.LayoutParams;
26
27import java.util.HashMap;
28
29/**
30 * ListRowPresenter renders {@link ListRow} using a
31 * {@link HorizontalGridView} hosted in a {@link ListRowView}.
32 *
33 * <h3>Hover card</h3>
34 * Optionally, {@link #setHoverCardPresenterSelector(PresenterSelector)} can be used to
35 * display a view for the currently focused list item below the rendered
36 * list. This view is known as a hover card.
37 *
38 * <h3>Selection animation</h3>
39 * ListRowPresenter disables {@link RowPresenter}'s default dimming effect and draws
40 * a dim overlay on each view individually.  A subclass may override and disable
41 * {@link #isUsingDefaultListSelectEffect()} and write its own dim effect in
42 * {@link #onSelectLevelChanged(RowPresenter.ViewHolder)}.
43 *
44 * <h3>Shadow</h3>
45 * ListRowPresenter applies a default shadow to each child view.  Call
46 * {@link #setShadowEnabled(boolean)} to disable shadows.  A subclass may override and return
47 * false in {@link #isUsingDefaultShadow()} and replace with its own shadow implementation.
48 */
49public class ListRowPresenter extends RowPresenter {
50
51    private static final String TAG = "ListRowPresenter";
52    private static final boolean DEBUG = false;
53
54    private static final int DEFAULT_RECYCLED_POOL_SIZE = 24;
55
56    /**
57     * ViewHolder for the ListRowPresenter.
58     */
59    public static class ViewHolder extends RowPresenter.ViewHolder {
60        final ListRowPresenter mListRowPresenter;
61        final HorizontalGridView mGridView;
62        ItemBridgeAdapter mItemBridgeAdapter;
63        final HorizontalHoverCardSwitcher mHoverCardViewSwitcher = new HorizontalHoverCardSwitcher();
64        final int mPaddingTop;
65        final int mPaddingBottom;
66        final int mPaddingLeft;
67        final int mPaddingRight;
68
69        public ViewHolder(View rootView, HorizontalGridView gridView, ListRowPresenter p) {
70            super(rootView);
71            mGridView = gridView;
72            mListRowPresenter = p;
73            mPaddingTop = mGridView.getPaddingTop();
74            mPaddingBottom = mGridView.getPaddingBottom();
75            mPaddingLeft = mGridView.getPaddingLeft();
76            mPaddingRight = mGridView.getPaddingRight();
77        }
78
79        public final ListRowPresenter getListRowPresenter() {
80            return mListRowPresenter;
81        }
82
83        public final HorizontalGridView getGridView() {
84            return mGridView;
85        }
86
87        public final ItemBridgeAdapter getBridgeAdapter() {
88            return mItemBridgeAdapter;
89        }
90    }
91
92    class ListRowPresenterItemBridgeAdapter extends ItemBridgeAdapter {
93        ListRowPresenter.ViewHolder mRowViewHolder;
94
95        ListRowPresenterItemBridgeAdapter(ListRowPresenter.ViewHolder rowViewHolder) {
96            mRowViewHolder = rowViewHolder;
97        }
98
99        @Override
100        protected void onCreate(ItemBridgeAdapter.ViewHolder viewHolder) {
101            if (mShadowOverlayHelper != null) {
102                mShadowOverlayHelper.onViewCreated(viewHolder.itemView);
103            }
104        }
105
106        @Override
107        public void onBind(final ItemBridgeAdapter.ViewHolder viewHolder) {
108            // Only when having an OnItemClickListner, we will attach the OnClickListener.
109            if (mRowViewHolder.getOnItemViewClickedListener() != null) {
110                viewHolder.mHolder.view.setOnClickListener(new View.OnClickListener() {
111                    @Override
112                    public void onClick(View v) {
113                        ItemBridgeAdapter.ViewHolder ibh = (ItemBridgeAdapter.ViewHolder)
114                                mRowViewHolder.mGridView.getChildViewHolder(viewHolder.itemView);
115                        if (mRowViewHolder.getOnItemViewClickedListener() != null) {
116                            mRowViewHolder.getOnItemViewClickedListener().onItemClicked(viewHolder.mHolder,
117                                    ibh.mItem, mRowViewHolder, (ListRow) mRowViewHolder.mRow);
118                        }
119                    }
120                });
121            }
122        }
123
124        @Override
125        public void onUnbind(ItemBridgeAdapter.ViewHolder viewHolder) {
126            if (mRowViewHolder.getOnItemViewClickedListener() != null) {
127                viewHolder.mHolder.view.setOnClickListener(null);
128            }
129        }
130
131        @Override
132        public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder) {
133            if (mShadowOverlayHelper != null && mShadowOverlayHelper.needsOverlay()) {
134                int dimmedColor = mRowViewHolder.mColorDimmer.getPaint().getColor();
135                mShadowOverlayHelper.setOverlayColor(viewHolder.itemView, dimmedColor);
136            }
137            mRowViewHolder.syncActivatedStatus(viewHolder.itemView);
138        }
139
140        @Override
141        public void onAddPresenter(Presenter presenter, int type) {
142            mRowViewHolder.getGridView().getRecycledViewPool().setMaxRecycledViews(
143                    type, getRecycledPoolSize(presenter));
144        }
145    }
146
147    private int mRowHeight;
148    private int mExpandedRowHeight;
149    private PresenterSelector mHoverCardPresenterSelector;
150    private int mFocusZoomFactor;
151    private boolean mUseFocusDimmer;
152    private boolean mShadowEnabled = true;
153    private int mBrowseRowsFadingEdgeLength = -1;
154    private boolean mRoundedCornersEnabled = true;
155    private HashMap<Presenter, Integer> mRecycledPoolSize = new HashMap<Presenter, Integer>();
156    private ShadowOverlayHelper mShadowOverlayHelper;
157
158    private static int sSelectedRowTopPadding;
159    private static int sExpandedSelectedRowTopPadding;
160    private static int sExpandedRowNoHovercardBottomPadding;
161
162    /**
163     * Constructs a ListRowPresenter with defaults.
164     * Uses {@link FocusHighlight#ZOOM_FACTOR_MEDIUM} for focus zooming and
165     * disabled dimming on focus.
166     */
167    public ListRowPresenter() {
168        this(FocusHighlight.ZOOM_FACTOR_MEDIUM);
169    }
170
171    /**
172     * Constructs a ListRowPresenter with the given parameters.
173     *
174     * @param focusZoomFactor Controls the zoom factor used when an item view is focused. One of
175     *         {@link FocusHighlight#ZOOM_FACTOR_NONE},
176     *         {@link FocusHighlight#ZOOM_FACTOR_SMALL},
177     *         {@link FocusHighlight#ZOOM_FACTOR_XSMALL},
178     *         {@link FocusHighlight#ZOOM_FACTOR_MEDIUM},
179     *         {@link FocusHighlight#ZOOM_FACTOR_LARGE}
180     * Dimming on focus defaults to disabled.
181     */
182    public ListRowPresenter(int focusZoomFactor) {
183        this(focusZoomFactor, false);
184    }
185
186    /**
187     * Constructs a ListRowPresenter with the given parameters.
188     *
189     * @param focusZoomFactor Controls the zoom factor used when an item view is focused. One of
190     *         {@link FocusHighlight#ZOOM_FACTOR_NONE},
191     *         {@link FocusHighlight#ZOOM_FACTOR_SMALL},
192     *         {@link FocusHighlight#ZOOM_FACTOR_XSMALL},
193     *         {@link FocusHighlight#ZOOM_FACTOR_MEDIUM},
194     *         {@link FocusHighlight#ZOOM_FACTOR_LARGE}
195     * @param useFocusDimmer determines if the FocusHighlighter will use the dimmer
196     */
197    public ListRowPresenter(int focusZoomFactor, boolean useFocusDimmer) {
198        if (!FocusHighlightHelper.isValidZoomIndex(focusZoomFactor)) {
199            throw new IllegalArgumentException("Unhandled zoom factor");
200        }
201        mFocusZoomFactor = focusZoomFactor;
202        mUseFocusDimmer = useFocusDimmer;
203    }
204
205    /**
206     * Sets the row height for rows created by this Presenter. Rows
207     * created before calling this method will not be updated.
208     *
209     * @param rowHeight Row height in pixels, or WRAP_CONTENT, or 0
210     * to use the default height.
211     */
212    public void setRowHeight(int rowHeight) {
213        mRowHeight = rowHeight;
214    }
215
216    /**
217     * Returns the row height for list rows created by this Presenter.
218     */
219    public int getRowHeight() {
220        return mRowHeight;
221    }
222
223    /**
224     * Sets the expanded row height for rows created by this Presenter.
225     * If not set, expanded rows have the same height as unexpanded
226     * rows.
227     *
228     * @param rowHeight The row height in to use when the row is expanded,
229     *        in pixels, or WRAP_CONTENT, or 0 to use the default.
230     */
231    public void setExpandedRowHeight(int rowHeight) {
232        mExpandedRowHeight = rowHeight;
233    }
234
235    /**
236     * Returns the expanded row height for rows created by this Presenter.
237     */
238    public int getExpandedRowHeight() {
239        return mExpandedRowHeight != 0 ? mExpandedRowHeight : mRowHeight;
240    }
241
242    /**
243     * Returns the zoom factor used for focus highlighting.
244     */
245    public final int getFocusZoomFactor() {
246        return mFocusZoomFactor;
247    }
248
249    /**
250     * Returns the zoom factor used for focus highlighting.
251     * @deprecated use {@link #getFocusZoomFactor} instead.
252     */
253    @Deprecated
254    public final int getZoomFactor() {
255        return mFocusZoomFactor;
256    }
257
258    /**
259     * Returns true if the focus dimmer is used for focus highlighting; false otherwise.
260     */
261    public final boolean isFocusDimmerUsed() {
262        return mUseFocusDimmer;
263    }
264
265    @Override
266    protected void initializeRowViewHolder(RowPresenter.ViewHolder holder) {
267        super.initializeRowViewHolder(holder);
268        final ViewHolder rowViewHolder = (ViewHolder) holder;
269        Context context = holder.view.getContext();
270        if (mShadowOverlayHelper == null) {
271            mShadowOverlayHelper = new ShadowOverlayHelper(context, needsDefaultListSelectEffect(),
272                    needsDefaultShadow(), areChildRoundedCornersEnabled(), isUsingZOrder(context));
273        }
274        rowViewHolder.mItemBridgeAdapter = new ListRowPresenterItemBridgeAdapter(rowViewHolder);
275        // set wrapper if needed
276        rowViewHolder.mItemBridgeAdapter.setWrapper(mShadowOverlayHelper.getWrapper());
277        mShadowOverlayHelper.prepareParentForShadow(rowViewHolder.mGridView);
278
279        FocusHighlightHelper.setupBrowseItemFocusHighlight(rowViewHolder.mItemBridgeAdapter,
280                mFocusZoomFactor, mUseFocusDimmer);
281        rowViewHolder.mGridView.setFocusDrawingOrderEnabled(mShadowOverlayHelper.getShadowType()
282                == ShadowOverlayHelper.SHADOW_STATIC);
283        rowViewHolder.mGridView.setOnChildSelectedListener(
284                new OnChildSelectedListener() {
285            @Override
286            public void onChildSelected(ViewGroup parent, View view, int position, long id) {
287                selectChildView(rowViewHolder, view, true);
288            }
289        });
290        rowViewHolder.mGridView.setOnUnhandledKeyListener(
291                new BaseGridView.OnUnhandledKeyListener() {
292            @Override
293            public boolean onUnhandledKey(KeyEvent event) {
294                if (rowViewHolder.getOnKeyListener() != null &&
295                        rowViewHolder.getOnKeyListener().onKey(
296                                rowViewHolder.view, event.getKeyCode(), event)) {
297                    return true;
298                }
299                return false;
300            }
301        });
302    }
303
304    final boolean needsDefaultListSelectEffect() {
305        return isUsingDefaultListSelectEffect() && getSelectEffectEnabled();
306    }
307
308    /**
309     * Sets the recycled pool size for the given presenter.
310     */
311    public void setRecycledPoolSize(Presenter presenter, int size) {
312        mRecycledPoolSize.put(presenter, size);
313    }
314
315    /**
316     * Returns the recycled pool size for the given presenter.
317     */
318    public int getRecycledPoolSize(Presenter presenter) {
319        return mRecycledPoolSize.containsKey(presenter) ? mRecycledPoolSize.get(presenter) :
320                DEFAULT_RECYCLED_POOL_SIZE;
321    }
322
323    /**
324     * Sets the {@link PresenterSelector} used for showing a select object in a hover card.
325     */
326    public final void setHoverCardPresenterSelector(PresenterSelector selector) {
327        mHoverCardPresenterSelector = selector;
328    }
329
330    /**
331     * Returns the {@link PresenterSelector} used for showing a select object in a hover card.
332     */
333    public final PresenterSelector getHoverCardPresenterSelector() {
334        return mHoverCardPresenterSelector;
335    }
336
337    /*
338     * Perform operations when a child of horizontal grid view is selected.
339     */
340    private void selectChildView(ViewHolder rowViewHolder, View view, boolean fireEvent) {
341        if (view != null) {
342            if (rowViewHolder.mExpanded && rowViewHolder.mSelected) {
343                ItemBridgeAdapter.ViewHolder ibh = (ItemBridgeAdapter.ViewHolder)
344                        rowViewHolder.mGridView.getChildViewHolder(view);
345
346                if (mHoverCardPresenterSelector != null) {
347                    rowViewHolder.mHoverCardViewSwitcher.select(
348                            rowViewHolder.mGridView, view, ibh.mItem);
349                }
350                if (fireEvent && rowViewHolder.getOnItemViewSelectedListener() != null) {
351                    rowViewHolder.getOnItemViewSelectedListener().onItemSelected(
352                            ibh.mHolder, ibh.mItem, rowViewHolder, rowViewHolder.mRow);
353                }
354            }
355        } else {
356            if (mHoverCardPresenterSelector != null) {
357                rowViewHolder.mHoverCardViewSwitcher.unselect();
358            }
359            if (fireEvent && rowViewHolder.getOnItemViewSelectedListener() != null) {
360                rowViewHolder.getOnItemViewSelectedListener().onItemSelected(
361                        null, null, rowViewHolder, rowViewHolder.mRow);
362            }
363        }
364    }
365
366    private static void initStatics(Context context) {
367        if (sSelectedRowTopPadding == 0) {
368            sSelectedRowTopPadding = context.getResources().getDimensionPixelSize(
369                    R.dimen.lb_browse_selected_row_top_padding);
370            sExpandedSelectedRowTopPadding = context.getResources().getDimensionPixelSize(
371                    R.dimen.lb_browse_expanded_selected_row_top_padding);
372            sExpandedRowNoHovercardBottomPadding = context.getResources().getDimensionPixelSize(
373                    R.dimen.lb_browse_expanded_row_no_hovercard_bottom_padding);
374        }
375    }
376
377    private int getSpaceUnderBaseline(ListRowPresenter.ViewHolder vh) {
378        RowHeaderPresenter.ViewHolder headerViewHolder = vh.getHeaderViewHolder();
379        if (headerViewHolder != null) {
380            if (getHeaderPresenter() != null) {
381                return getHeaderPresenter().getSpaceUnderBaseline(headerViewHolder);
382            }
383            return headerViewHolder.view.getPaddingBottom();
384        }
385        return 0;
386    }
387
388    private void setVerticalPadding(ListRowPresenter.ViewHolder vh) {
389        int paddingTop, paddingBottom;
390        // Note: sufficient bottom padding needed for card shadows.
391        if (vh.isExpanded()) {
392            int headerSpaceUnderBaseline = getSpaceUnderBaseline(vh);
393            if (DEBUG) Log.v(TAG, "headerSpaceUnderBaseline " + headerSpaceUnderBaseline);
394            paddingTop = (vh.isSelected() ? sExpandedSelectedRowTopPadding : vh.mPaddingTop) -
395                    headerSpaceUnderBaseline;
396            paddingBottom = mHoverCardPresenterSelector == null ?
397                    sExpandedRowNoHovercardBottomPadding : vh.mPaddingBottom;
398        } else if (vh.isSelected()) {
399            paddingTop = sSelectedRowTopPadding - vh.mPaddingBottom;
400            paddingBottom = sSelectedRowTopPadding;
401        } else {
402            paddingTop = 0;
403            paddingBottom = vh.mPaddingBottom;
404        }
405        vh.getGridView().setPadding(vh.mPaddingLeft, paddingTop, vh.mPaddingRight,
406                paddingBottom);
407    }
408
409    @Override
410    protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) {
411        initStatics(parent.getContext());
412        ListRowView rowView = new ListRowView(parent.getContext());
413        setupFadingEffect(rowView);
414        if (mRowHeight != 0) {
415            rowView.getGridView().setRowHeight(mRowHeight);
416        }
417        return new ViewHolder(rowView, rowView.getGridView(), this);
418    }
419
420    /**
421     * Dispatch item selected event using current selected item in the {@link HorizontalGridView}.
422     * The method should only be called from onRowViewSelected().
423     */
424    @Override
425    protected void dispatchItemSelectedListener(RowPresenter.ViewHolder holder, boolean selected) {
426        ViewHolder vh = (ViewHolder)holder;
427        ItemBridgeAdapter.ViewHolder itemViewHolder = (ItemBridgeAdapter.ViewHolder)
428                vh.mGridView.findViewHolderForPosition(vh.mGridView.getSelectedPosition());
429        if (itemViewHolder == null) {
430            super.dispatchItemSelectedListener(holder, selected);
431            return;
432        }
433
434        if (selected) {
435            if (holder.getOnItemViewSelectedListener() != null) {
436                holder.getOnItemViewSelectedListener().onItemSelected(
437                        itemViewHolder.getViewHolder(), itemViewHolder.mItem, vh, vh.getRow());
438            }
439        }
440    }
441
442    @Override
443    protected void onRowViewSelected(RowPresenter.ViewHolder holder, boolean selected) {
444        super.onRowViewSelected(holder, selected);
445        ViewHolder vh = (ViewHolder) holder;
446        setVerticalPadding(vh);
447        updateFooterViewSwitcher(vh);
448    }
449
450    /*
451     * Show or hide hover card when row selection or expanded state is changed.
452     */
453    private void updateFooterViewSwitcher(ViewHolder vh) {
454        if (vh.mExpanded && vh.mSelected) {
455            if (mHoverCardPresenterSelector != null) {
456                vh.mHoverCardViewSwitcher.init((ViewGroup) vh.view,
457                        mHoverCardPresenterSelector);
458            }
459            ItemBridgeAdapter.ViewHolder ibh = (ItemBridgeAdapter.ViewHolder)
460                    vh.mGridView.findViewHolderForPosition(
461                            vh.mGridView.getSelectedPosition());
462            selectChildView(vh, ibh == null ? null : ibh.itemView, false);
463        } else {
464            if (mHoverCardPresenterSelector != null) {
465                vh.mHoverCardViewSwitcher.unselect();
466            }
467        }
468    }
469
470    private void setupFadingEffect(ListRowView rowView) {
471        // content is completely faded at 1/2 padding of left, fading length is 1/2 of padding.
472        HorizontalGridView gridView = rowView.getGridView();
473        if (mBrowseRowsFadingEdgeLength < 0) {
474            TypedArray ta = gridView.getContext()
475                    .obtainStyledAttributes(R.styleable.LeanbackTheme);
476            mBrowseRowsFadingEdgeLength = (int) ta.getDimension(
477                    R.styleable.LeanbackTheme_browseRowsFadingEdgeLength, 0);
478            ta.recycle();
479        }
480        gridView.setFadingLeftEdgeLength(mBrowseRowsFadingEdgeLength);
481    }
482
483    @Override
484    protected void onRowViewExpanded(RowPresenter.ViewHolder holder, boolean expanded) {
485        super.onRowViewExpanded(holder, expanded);
486        ViewHolder vh = (ViewHolder) holder;
487        if (getRowHeight() != getExpandedRowHeight()) {
488            int newHeight = expanded ? getExpandedRowHeight() : getRowHeight();
489            vh.getGridView().setRowHeight(newHeight);
490        }
491        setVerticalPadding(vh);
492        updateFooterViewSwitcher(vh);
493    }
494
495    @Override
496    protected void onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item) {
497        super.onBindRowViewHolder(holder, item);
498        ViewHolder vh = (ViewHolder) holder;
499        ListRow rowItem = (ListRow) item;
500        vh.mItemBridgeAdapter.setAdapter(rowItem.getAdapter());
501        vh.mGridView.setAdapter(vh.mItemBridgeAdapter);
502    }
503
504    @Override
505    protected void onUnbindRowViewHolder(RowPresenter.ViewHolder holder) {
506        ViewHolder vh = (ViewHolder) holder;
507        vh.mGridView.setAdapter(null);
508        vh.mItemBridgeAdapter.clear();
509        super.onUnbindRowViewHolder(holder);
510    }
511
512    /**
513     * ListRowPresenter overrides the default select effect of {@link RowPresenter}
514     * and return false.
515     */
516    @Override
517    public final boolean isUsingDefaultSelectEffect() {
518        return false;
519    }
520
521    /**
522     * Returns true so that default select effect is applied to each individual
523     * child of {@link HorizontalGridView}.  Subclass may return false to disable
524     * the default implementation.
525     * @see #onSelectLevelChanged(RowPresenter.ViewHolder)
526     */
527    public boolean isUsingDefaultListSelectEffect() {
528        return true;
529    }
530
531    /**
532     * Returns true if SDK >= 18, where default shadow
533     * is applied to each individual child of {@link HorizontalGridView}.
534     * Subclass may return false to disable.
535     */
536    public boolean isUsingDefaultShadow() {
537        return ShadowOverlayHelper.supportsShadow();
538    }
539
540    /**
541     * Returns true if SDK >= L, where Z shadow is enabled so that Z order is enabled
542     * on each child of horizontal list.   If subclass returns false in isUsingDefaultShadow()
543     * and does not use Z-shadow on SDK >= L, it should override isUsingZOrder() return false.
544     */
545    public boolean isUsingZOrder(Context context) {
546        return !Settings.getInstance(context).preferStaticShadows();
547    }
548
549    /**
550     * Enables or disables child shadow.
551     * This is not only for enable/disable default shadow implementation but also subclass must
552     * respect this flag.
553     */
554    public final void setShadowEnabled(boolean enabled) {
555        mShadowEnabled = enabled;
556    }
557
558    /**
559     * Returns true if child shadow is enabled.
560     * This is not only for enable/disable default shadow implementation but also subclass must
561     * respect this flag.
562     */
563    public final boolean getShadowEnabled() {
564        return mShadowEnabled;
565    }
566
567    /**
568     * Enables or disabled rounded corners on children of this row.
569     * Supported on Android SDK >= L.
570     */
571    public final void enableChildRoundedCorners(boolean enable) {
572        mRoundedCornersEnabled = enable;
573    }
574
575    /**
576     * Returns true if rounded corners are enabled for children of this row.
577     */
578    public final boolean areChildRoundedCornersEnabled() {
579        return mRoundedCornersEnabled;
580    }
581
582    final boolean needsDefaultShadow() {
583        return isUsingDefaultShadow() && getShadowEnabled();
584    }
585
586    @Override
587    public boolean canDrawOutOfBounds() {
588        return needsDefaultShadow();
589    }
590
591    /**
592     * Applies select level to header and draw a default color dim over each child
593     * of {@link HorizontalGridView}.
594     * <p>
595     * Subclass may override this method.  A subclass
596     * needs to call super.onSelectLevelChanged() for applying header select level
597     * and optionally applying a default select level to each child view of
598     * {@link HorizontalGridView} if {@link #isUsingDefaultListSelectEffect()}
599     * is true.  Subclass may override {@link #isUsingDefaultListSelectEffect()} to return
600     * false and deal with the individual item select level by itself.
601     * </p>
602     */
603    @Override
604    protected void onSelectLevelChanged(RowPresenter.ViewHolder holder) {
605        super.onSelectLevelChanged(holder);
606        if (mShadowOverlayHelper != null && mShadowOverlayHelper.needsOverlay()) {
607            ViewHolder vh = (ViewHolder) holder;
608            int dimmedColor = vh.mColorDimmer.getPaint().getColor();
609            for (int i = 0, count = vh.mGridView.getChildCount(); i < count; i++) {
610                mShadowOverlayHelper.setOverlayColor(vh.mGridView.getChildAt(i), dimmedColor);
611            }
612            if (vh.mGridView.getFadingLeftEdge()) {
613                vh.mGridView.invalidate();
614            }
615        }
616    }
617
618    @Override
619    public void freeze(RowPresenter.ViewHolder holder, boolean freeze) {
620        ViewHolder vh = (ViewHolder) holder;
621        vh.mGridView.setScrollEnabled(!freeze);
622    }
623
624    @Override
625    public void setEntranceTransitionState(RowPresenter.ViewHolder holder,
626            boolean afterEntrance) {
627        super.setEntranceTransitionState(holder, afterEntrance);
628        ((ViewHolder) holder).mGridView.setChildrenVisibility(
629                afterEntrance? View.VISIBLE : View.INVISIBLE);
630    }
631}
632