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