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