• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.support.v17.leanback.widget;
15 
16 import android.content.Context;
17 import android.content.res.TypedArray;
18 import android.support.v17.leanback.R;
19 import android.support.v17.leanback.system.Settings;
20 import android.support.v17.leanback.transition.TransitionHelper;
21 import android.support.v7.widget.RecyclerView;
22 import android.util.Log;
23 import android.view.KeyEvent;
24 import android.view.View;
25 import android.view.ViewGroup;
26 
27 import 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  */
49 public 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 
ViewHolder(View rootView, HorizontalGridView gridView, ListRowPresenter p)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          */
getListRowPresenter()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          */
getGridView()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          */
getBridgeAdapter()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          */
getSelectedPosition()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          */
getItemViewHolder(int position)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 
SelectItemViewHolderTask(int itemPosition)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          */
setItemPosition(int itemPosition)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          */
getItemPosition()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          */
setSmoothScroll(boolean smoothScroll)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          */
isSmoothScroll()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          */
getItemTask()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          */
setItemTask(Presenter.ViewHolderTask itemTask)186         public void setItemTask(Presenter.ViewHolderTask itemTask) {
187             mItemTask = itemTask;
188         }
189 
190         @Override
run(Presenter.ViewHolder holder)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 
ListRowPresenterItemBridgeAdapter(ListRowPresenter.ViewHolder rowViewHolder)217         ListRowPresenterItemBridgeAdapter(ListRowPresenter.ViewHolder rowViewHolder) {
218             mRowViewHolder = rowViewHolder;
219         }
220 
221         @Override
onCreate(ItemBridgeAdapter.ViewHolder viewHolder)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
onBind(final ItemBridgeAdapter.ViewHolder viewHolder)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
onUnbind(ItemBridgeAdapter.ViewHolder viewHolder)250         public void onUnbind(ItemBridgeAdapter.ViewHolder viewHolder) {
251             if (mRowViewHolder.getOnItemViewClickedListener() != null) {
252                 viewHolder.mHolder.view.setOnClickListener(null);
253             }
254         }
255 
256         @Override
onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder)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
onAddPresenter(Presenter presenter, int type)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      */
ListRowPresenter()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      */
ListRowPresenter(int focusZoomFactor)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      */
ListRowPresenter(int focusZoomFactor, boolean useFocusDimmer)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      */
setRowHeight(int rowHeight)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      */
getRowHeight()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      */
setExpandedRowHeight(int rowHeight)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      */
getExpandedRowHeight()366     public int getExpandedRowHeight() {
367         return mExpandedRowHeight != 0 ? mExpandedRowHeight : mRowHeight;
368     }
369 
370     /**
371      * Returns the zoom factor used for focus highlighting.
372      */
getFocusZoomFactor()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
getZoomFactor()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      */
isFocusDimmerUsed()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      */
setNumRows(int numRows)397     public void setNumRows(int numRows) {
398         this.mNumRows = numRows;
399     }
400 
401     @Override
initializeRowViewHolder(RowPresenter.ViewHolder holder)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 
needsDefaultListSelectEffect()451     final boolean needsDefaultListSelectEffect() {
452         return isUsingDefaultListSelectEffect() && getSelectEffectEnabled();
453     }
454 
455     /**
456      * Sets the recycled pool size for the given presenter.
457      */
setRecycledPoolSize(Presenter presenter, int size)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      */
getRecycledPoolSize(Presenter presenter)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      */
setHoverCardPresenterSelector(PresenterSelector selector)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      */
getHoverCardPresenterSelector()480     public final PresenterSelector getHoverCardPresenterSelector() {
481         return mHoverCardPresenterSelector;
482     }
483 
484     /*
485      * Perform operations when a child of horizontal grid view is selected.
486      */
selectChildView(ViewHolder rowViewHolder, View view, boolean fireEvent)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 
initStatics(Context context)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 
getSpaceUnderBaseline(ListRowPresenter.ViewHolder vh)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 
setVerticalPadding(ListRowPresenter.ViewHolder vh)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
createRowViewHolder(ViewGroup parent)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
dispatchItemSelectedListener(RowPresenter.ViewHolder holder, boolean selected)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
onRowViewSelected(RowPresenter.ViewHolder holder, boolean selected)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      */
updateFooterViewSwitcher(ViewHolder vh)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 
setupFadingEffect(ListRowView rowView)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
onRowViewExpanded(RowPresenter.ViewHolder holder, boolean expanded)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
onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item)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
onUnbindRowViewHolder(RowPresenter.ViewHolder holder)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
isUsingDefaultSelectEffect()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      */
isUsingDefaultListSelectEffect()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      */
isUsingDefaultShadow()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      */
isUsingZOrder(Context context)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      */
setShadowEnabled(boolean enabled)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      */
getShadowEnabled()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      */
enableChildRoundedCorners(boolean enable)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      */
areChildRoundedCornersEnabled()726     public final boolean areChildRoundedCornersEnabled() {
727         return mRoundedCornersEnabled;
728     }
729 
needsDefaultShadow()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      */
setKeepChildForeground(boolean keep)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      */
isKeepChildForeground()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 rounded corner.
766      */
createShadowOverlayOptions()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
onSelectLevelChanged(RowPresenter.ViewHolder holder)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
freeze(RowPresenter.ViewHolder holder, boolean freeze)799     public void freeze(RowPresenter.ViewHolder holder, boolean freeze) {
800         ViewHolder vh = (ViewHolder) holder;
801         vh.mGridView.setScrollEnabled(!freeze);
802     }
803 
804     @Override
setEntranceTransitionState(RowPresenter.ViewHolder holder, boolean afterEntrance)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