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