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