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