• 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>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  */
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         @Override
getSelectedItemViewHolder()127         public Presenter.ViewHolder getSelectedItemViewHolder() {
128             return getItemViewHolder(getSelectedPosition());
129         }
130 
131         @Override
getSelectedItem()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 
SelectItemViewHolderTask(int itemPosition)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          */
setItemPosition(int itemPosition)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          */
getItemPosition()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          */
setSmoothScroll(boolean smoothScroll)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          */
isSmoothScroll()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          */
getItemTask()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          */
setItemTask(Presenter.ViewHolderTask itemTask)201         public void setItemTask(Presenter.ViewHolderTask itemTask) {
202             mItemTask = itemTask;
203         }
204 
205         @Override
run(Presenter.ViewHolder holder)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 
ListRowPresenterItemBridgeAdapter(ListRowPresenter.ViewHolder rowViewHolder)232         ListRowPresenterItemBridgeAdapter(ListRowPresenter.ViewHolder rowViewHolder) {
233             mRowViewHolder = rowViewHolder;
234         }
235 
236         @Override
onCreate(ItemBridgeAdapter.ViewHolder viewHolder)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
onBind(final ItemBridgeAdapter.ViewHolder viewHolder)247         public void onBind(final ItemBridgeAdapter.ViewHolder viewHolder) {
248             // Only when having an OnItemClickListener, 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
onUnbind(ItemBridgeAdapter.ViewHolder viewHolder)265         public void onUnbind(ItemBridgeAdapter.ViewHolder viewHolder) {
266             if (mRowViewHolder.getOnItemViewClickedListener() != null) {
267                 viewHolder.mHolder.view.setOnClickListener(null);
268             }
269         }
270 
271         @Override
onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder)272         public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder) {
273             applySelectLevelToChild(mRowViewHolder, viewHolder.itemView);
274             mRowViewHolder.syncActivatedStatus(viewHolder.itemView);
275         }
276 
277         @Override
onAddPresenter(Presenter presenter, int type)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      */
ListRowPresenter()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      */
ListRowPresenter(int focusZoomFactor)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      */
ListRowPresenter(int focusZoomFactor, boolean useFocusDimmer)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      */
setRowHeight(int rowHeight)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      */
getRowHeight()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      */
setExpandedRowHeight(int rowHeight)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      */
getExpandedRowHeight()378     public int getExpandedRowHeight() {
379         return mExpandedRowHeight != 0 ? mExpandedRowHeight : mRowHeight;
380     }
381 
382     /**
383      * Returns the zoom factor used for focus highlighting.
384      */
getFocusZoomFactor()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
getZoomFactor()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      */
isFocusDimmerUsed()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      */
setNumRows(int numRows)409     public void setNumRows(int numRows) {
410         this.mNumRows = numRows;
411     }
412 
413     @Override
initializeRowViewHolder(RowPresenter.ViewHolder holder)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 
needsDefaultListSelectEffect()461     final boolean needsDefaultListSelectEffect() {
462         return isUsingDefaultListSelectEffect() && getSelectEffectEnabled();
463     }
464 
465     /**
466      * Sets the recycled pool size for the given presenter.
467      */
setRecycledPoolSize(Presenter presenter, int size)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      */
getRecycledPoolSize(Presenter presenter)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      */
setHoverCardPresenterSelector(PresenterSelector selector)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      */
getHoverCardPresenterSelector()490     public final PresenterSelector getHoverCardPresenterSelector() {
491         return mHoverCardPresenterSelector;
492     }
493 
494     /*
495      * Perform operations when a child of horizontal grid view is selected.
496      */
selectChildView(ViewHolder rowViewHolder, View view, boolean fireEvent)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 
initStatics(Context context)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 
getSpaceUnderBaseline(ListRowPresenter.ViewHolder vh)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 
setVerticalPadding(ListRowPresenter.ViewHolder vh)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
createRowViewHolder(ViewGroup parent)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
dispatchItemSelectedListener(RowPresenter.ViewHolder holder, boolean selected)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
onRowViewSelected(RowPresenter.ViewHolder holder, boolean selected)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      */
updateFooterViewSwitcher(ViewHolder vh)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 
setupFadingEffect(ListRowView rowView)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
onRowViewExpanded(RowPresenter.ViewHolder holder, boolean expanded)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
onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item)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
onUnbindRowViewHolder(RowPresenter.ViewHolder holder)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
isUsingDefaultSelectEffect()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      */
isUsingDefaultListSelectEffect()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      */
isUsingDefaultShadow()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      */
isUsingZOrder(Context context)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      */
isUsingOutlineClipping(Context context)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      */
setShadowEnabled(boolean enabled)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      */
getShadowEnabled()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      */
enableChildRoundedCorners(boolean enable)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      */
areChildRoundedCornersEnabled()749     public final boolean areChildRoundedCornersEnabled() {
750         return mRoundedCornersEnabled;
751     }
752 
needsDefaultShadow()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      */
setKeepChildForeground(boolean keep)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      */
isKeepChildForeground()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      */
createShadowOverlayOptions()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
onSelectLevelChanged(RowPresenter.ViewHolder holder)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      */
applySelectLevelToChild(ViewHolder rowViewHolder, View childView)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
freeze(RowPresenter.ViewHolder holder, boolean freeze)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
setEntranceTransitionState(RowPresenter.ViewHolder holder, boolean afterEntrance)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