• 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.app.Activity;
17 import android.content.Context;
18 import android.content.res.Resources;
19 import android.graphics.Bitmap;
20 import android.graphics.Color;
21 import android.graphics.Rect;
22 import android.graphics.drawable.Drawable;
23 import android.graphics.drawable.BitmapDrawable;
24 import android.graphics.drawable.ColorDrawable;
25 import android.os.Handler;
26 import android.support.v17.leanback.R;
27 import android.support.v17.leanback.widget.ListRowPresenter.ViewHolder;
28 import android.support.v7.widget.RecyclerView;
29 import android.util.Log;
30 import android.util.TypedValue;
31 import android.view.KeyEvent;
32 import android.view.LayoutInflater;
33 import android.view.View;
34 import android.view.ViewGroup;
35 import android.view.ViewGroup.MarginLayoutParams;
36 import android.widget.FrameLayout;
37 import android.widget.ImageView;
38 
39 import java.util.Collection;
40 
41 /**
42  * Renders a {@link DetailsOverviewRow} to display an overview of an item. Typically this row will
43  * be the first row in a fragment such as the
44  * {@link android.support.v17.leanback.app.DetailsFragment}. The View created by the
45  * FullWidthDetailsOverviewRowPresenter is made in three parts: logo view on the left, action list view on
46  * the top and a customizable detailed description view on the right.
47  *
48  * <p>The detailed description is rendered using a {@link Presenter} passed in
49  * {@link #FullWidthDetailsOverviewRowPresenter(Presenter)}. Typically this will be an instance of
50  * {@link AbstractDetailsDescriptionPresenter}. The application can access the detailed description
51  * ViewHolder from {@link ViewHolder#getDetailsDescriptionViewHolder()}.
52  * </p>
53  *
54  * <p>The logo view is rendered using a customizable {@link DetailsOverviewLogoPresenter} passed in
55  * {@link #FullWidthDetailsOverviewRowPresenter(Presenter, DetailsOverviewLogoPresenter)}. The application
56  * can access the logo ViewHolder from {@link ViewHolder#getLogoViewHolder()}.
57  * </p>
58  *
59  * <p>
60  * To support activity shared element transition, call {@link #setListener(Listener)} with
61  * {@link FullWidthDetailsOverviewSharedElementHelper} during Activity's onCreate(). Application is free to
62  * create its own "shared element helper" class using the Listener for image binding.
63  * Call {@link #setParticipatingEntranceTransition(boolean)} with false
64  * </p>
65  *
66  * <p>
67  * The view has three states: {@link #STATE_HALF} {@link #STATE_FULL} and {@link #STATE_SMALL}. See
68  * {@link android.support.v17.leanback.app.DetailsFragment} where it switches states based on
69  * selected row position.
70  * </p>
71  */
72 public class FullWidthDetailsOverviewRowPresenter extends RowPresenter {
73 
74     private static final String TAG = "FullWidthDetailsOverviewRowPresenter";
75     private static final boolean DEBUG = false;
76 
77     private static Rect sTmpRect = new Rect();
78     private static final Handler sHandler = new Handler();
79 
80     /**
81      * This is the default state corresponding to layout file.  The view takes full width
82      * of screen and covers bottom half of the screen.
83      */
84     public static final int STATE_HALF = 0;
85     /**
86      * This is the state when the view covers full width and height of screen.
87      */
88     public static final int STATE_FULL = 1;
89     /**
90      * This is the state where the view shrinks to a small banner.
91      */
92     public static final int STATE_SMALL = 2;
93 
94     /**
95      * This is the alignment mode that the logo and description align to the starting edge of the
96      * overview view.
97      */
98     public static final int ALIGN_MODE_START = 0;
99     /**
100      * This is the alignment mode that the ending edge of logo and the starting edge of description
101      * align to the middle of the overview view. Note that this might not be the exact horizontal
102      * center of the overview view.
103      */
104     public static final int ALIGN_MODE_MIDDLE = 1;
105 
106     /**
107      * Listeners for events on ViewHolder.
108      */
109     public static abstract class Listener {
110 
111         /**
112          * {@link FullWidthDetailsOverviewRowPresenter#notifyOnBindLogo(ViewHolder)} is called.
113          * @param vh  The ViewHolder that has bound logo view.
114          */
onBindLogo(ViewHolder vh)115         public void onBindLogo(ViewHolder vh) {
116         }
117 
118     }
119 
120     class ActionsItemBridgeAdapter extends ItemBridgeAdapter {
121         FullWidthDetailsOverviewRowPresenter.ViewHolder mViewHolder;
122 
ActionsItemBridgeAdapter(FullWidthDetailsOverviewRowPresenter.ViewHolder viewHolder)123         ActionsItemBridgeAdapter(FullWidthDetailsOverviewRowPresenter.ViewHolder viewHolder) {
124             mViewHolder = viewHolder;
125         }
126 
127         @Override
onBind(final ItemBridgeAdapter.ViewHolder ibvh)128         public void onBind(final ItemBridgeAdapter.ViewHolder ibvh) {
129             if (mViewHolder.getOnItemViewClickedListener() != null ||
130                     mActionClickedListener != null) {
131                 ibvh.getPresenter().setOnClickListener(
132                         ibvh.getViewHolder(), new View.OnClickListener() {
133                             @Override
134                             public void onClick(View v) {
135                                 if (mViewHolder.getOnItemViewClickedListener() != null) {
136                                     mViewHolder.getOnItemViewClickedListener().onItemClicked(
137                                             ibvh.getViewHolder(), ibvh.getItem(),
138                                             mViewHolder, mViewHolder.getRow());
139                                 }
140                                 if (mActionClickedListener != null) {
141                                     mActionClickedListener.onActionClicked((Action) ibvh.getItem());
142                                 }
143                             }
144                         });
145             }
146         }
147         @Override
onUnbind(final ItemBridgeAdapter.ViewHolder ibvh)148         public void onUnbind(final ItemBridgeAdapter.ViewHolder ibvh) {
149             if (mViewHolder.getOnItemViewClickedListener() != null ||
150                     mActionClickedListener != null) {
151                 ibvh.getPresenter().setOnClickListener(ibvh.getViewHolder(), null);
152             }
153         }
154         @Override
onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder)155         public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder) {
156             // Remove first to ensure we don't add ourselves more than once.
157             viewHolder.itemView.removeOnLayoutChangeListener(mViewHolder.mLayoutChangeListener);
158             viewHolder.itemView.addOnLayoutChangeListener(mViewHolder.mLayoutChangeListener);
159         }
160         @Override
onDetachedFromWindow(ItemBridgeAdapter.ViewHolder viewHolder)161         public void onDetachedFromWindow(ItemBridgeAdapter.ViewHolder viewHolder) {
162             viewHolder.itemView.removeOnLayoutChangeListener(mViewHolder.mLayoutChangeListener);
163             mViewHolder.checkFirstAndLastPosition(false);
164         }
165     }
166 
167     /**
168      * A ViewHolder for the DetailsOverviewRow.
169      */
170     public class ViewHolder extends RowPresenter.ViewHolder {
171 
172         protected final DetailsOverviewRow.Listener mRowListener = createRowListener();
173 
createRowListener()174         protected DetailsOverviewRow.Listener createRowListener() {
175             return new DetailsOverviewRowListener();
176         }
177 
178         public class DetailsOverviewRowListener extends DetailsOverviewRow.Listener {
179             @Override
onImageDrawableChanged(DetailsOverviewRow row)180             public void onImageDrawableChanged(DetailsOverviewRow row) {
181                 sHandler.removeCallbacks(mUpdateDrawableCallback);
182                 sHandler.post(mUpdateDrawableCallback);
183             }
184 
185             @Override
onItemChanged(DetailsOverviewRow row)186             public void onItemChanged(DetailsOverviewRow row) {
187                 if (mDetailsDescriptionViewHolder != null) {
188                     mDetailsPresenter.onUnbindViewHolder(mDetailsDescriptionViewHolder);
189                 }
190                 mDetailsPresenter.onBindViewHolder(mDetailsDescriptionViewHolder, row.getItem());
191             }
192 
193             @Override
onActionsAdapterChanged(DetailsOverviewRow row)194             public void onActionsAdapterChanged(DetailsOverviewRow row) {
195                 bindActions(row.getActionsAdapter());
196             }
197         };
198 
199         final ViewGroup mOverviewRoot;
200         final FrameLayout mOverviewFrame;
201         final ViewGroup mDetailsDescriptionFrame;
202         final HorizontalGridView mActionsRow;
203         final Presenter.ViewHolder mDetailsDescriptionViewHolder;
204         final DetailsOverviewLogoPresenter.ViewHolder mDetailsLogoViewHolder;
205         int mNumItems;
206         ItemBridgeAdapter mActionBridgeAdapter;
207         int mState = STATE_HALF;
208 
209         final Runnable mUpdateDrawableCallback = new Runnable() {
210             @Override
211             public void run() {
212                 Row row = getRow();
213                 if (row == null) {
214                     return;
215                 }
216                 mDetailsOverviewLogoPresenter.onBindViewHolder(mDetailsLogoViewHolder, row);
217             }
218         };
219 
bindActions(ObjectAdapter adapter)220         void bindActions(ObjectAdapter adapter) {
221             mActionBridgeAdapter.setAdapter(adapter);
222             mActionsRow.setAdapter(mActionBridgeAdapter);
223             mNumItems = mActionBridgeAdapter.getItemCount();
224 
225         }
226 
onBind()227         void onBind() {
228             DetailsOverviewRow row = (DetailsOverviewRow) getRow();
229             bindActions(row.getActionsAdapter());
230             row.addListener(mRowListener);
231         }
232 
onUnbind()233         void onUnbind() {
234             DetailsOverviewRow row = (DetailsOverviewRow) getRow();
235             row.removeListener(mRowListener);
236             sHandler.removeCallbacks(mUpdateDrawableCallback);
237         }
238 
239         final View.OnLayoutChangeListener mLayoutChangeListener =
240                 new View.OnLayoutChangeListener() {
241 
242             @Override
243             public void onLayoutChange(View v, int left, int top, int right,
244                     int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
245                 if (DEBUG) Log.v(TAG, "onLayoutChange " + v);
246                 checkFirstAndLastPosition(false);
247             }
248         };
249 
250         final OnChildSelectedListener mChildSelectedListener = new OnChildSelectedListener() {
251             @Override
252             public void onChildSelected(ViewGroup parent, View view, int position, long id) {
253                 dispatchItemSelection(view);
254             }
255         };
256 
dispatchItemSelection(View view)257         void dispatchItemSelection(View view) {
258             if (!isSelected()) {
259                 return;
260             }
261             ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder) (view != null ?
262                     mActionsRow.getChildViewHolder(view) :
263                     mActionsRow.findViewHolderForPosition(mActionsRow.getSelectedPosition()));
264             if (ibvh == null) {
265                 if (getOnItemViewSelectedListener() != null) {
266                     getOnItemViewSelectedListener().onItemSelected(null, null,
267                             ViewHolder.this, getRow());
268                 }
269             } else {
270                 if (getOnItemViewSelectedListener() != null) {
271                     getOnItemViewSelectedListener().onItemSelected(ibvh.getViewHolder(), ibvh.getItem(),
272                             ViewHolder.this, getRow());
273                 }
274             }
275         };
276 
277         final RecyclerView.OnScrollListener mScrollListener =
278                 new RecyclerView.OnScrollListener() {
279 
280             @Override
281             public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
282             }
283             @Override
284             public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
285                 checkFirstAndLastPosition(true);
286             }
287         };
288 
getViewCenter(View view)289         private int getViewCenter(View view) {
290             return (view.getRight() - view.getLeft()) / 2;
291         }
292 
checkFirstAndLastPosition(boolean fromScroll)293         private void checkFirstAndLastPosition(boolean fromScroll) {
294             RecyclerView.ViewHolder viewHolder;
295 
296             viewHolder = mActionsRow.findViewHolderForPosition(mNumItems - 1);
297             boolean showRight = (viewHolder == null ||
298                     viewHolder.itemView.getRight() > mActionsRow.getWidth());
299 
300             viewHolder = mActionsRow.findViewHolderForPosition(0);
301             boolean showLeft = (viewHolder == null || viewHolder.itemView.getLeft() < 0);
302 
303             if (DEBUG) Log.v(TAG, "checkFirstAndLast fromScroll " + fromScroll +
304                     " showRight " + showRight + " showLeft " + showLeft);
305 
306         }
307 
308         /**
309          * Constructor for the ViewHolder.
310          *
311          * @param rootView The root View that this view holder will be attached
312          *        to.
313          */
ViewHolder(View rootView, Presenter detailsPresenter, DetailsOverviewLogoPresenter logoPresenter)314         public ViewHolder(View rootView, Presenter detailsPresenter,
315                 DetailsOverviewLogoPresenter logoPresenter) {
316             super(rootView);
317             mOverviewRoot = (ViewGroup) rootView.findViewById(R.id.details_root);
318             mOverviewFrame = (FrameLayout) rootView.findViewById(R.id.details_frame);
319             mDetailsDescriptionFrame =
320                     (ViewGroup) rootView.findViewById(R.id.details_overview_description);
321             mActionsRow =
322                     (HorizontalGridView) mOverviewFrame.findViewById(R.id.details_overview_actions);
323             mActionsRow.setHasOverlappingRendering(false);
324             mActionsRow.setOnScrollListener(mScrollListener);
325             mActionsRow.setAdapter(mActionBridgeAdapter);
326             mActionsRow.setOnChildSelectedListener(mChildSelectedListener);
327 
328             final int fadeLength = rootView.getResources().getDimensionPixelSize(
329                     R.dimen.lb_details_overview_actions_fade_size);
330             mActionsRow.setFadingRightEdgeLength(fadeLength);
331             mActionsRow.setFadingLeftEdgeLength(fadeLength);
332             mDetailsDescriptionViewHolder =
333                     detailsPresenter.onCreateViewHolder(mDetailsDescriptionFrame);
334             mDetailsDescriptionFrame.addView(mDetailsDescriptionViewHolder.view);
335             mDetailsLogoViewHolder = (DetailsOverviewLogoPresenter.ViewHolder)
336                     logoPresenter.onCreateViewHolder(mOverviewRoot);
337             mOverviewRoot.addView(mDetailsLogoViewHolder.view);
338         }
339 
340         /**
341          * Returns the rectangle area with a color background.
342          */
getOverviewView()343         public final ViewGroup getOverviewView() {
344             return mOverviewFrame;
345         }
346 
347         /**
348          * Returns the ViewHolder for logo.
349          */
getLogoViewHolder()350         public final DetailsOverviewLogoPresenter.ViewHolder getLogoViewHolder() {
351             return mDetailsLogoViewHolder;
352         }
353 
354         /**
355          * Returns the ViewHolder for DetailsDescription.
356          */
getDetailsDescriptionViewHolder()357         public final Presenter.ViewHolder getDetailsDescriptionViewHolder() {
358             return mDetailsDescriptionViewHolder;
359         }
360 
361         /**
362          * Returns the root view for inserting details description.
363          */
getDetailsDescriptionFrame()364         public final ViewGroup getDetailsDescriptionFrame() {
365             return mDetailsDescriptionFrame;
366         }
367 
368         /**
369          * Returns the view of actions row.
370          */
getActionsRow()371         public final ViewGroup getActionsRow() {
372             return mActionsRow;
373         }
374 
375         /**
376          * Returns current state of the ViewHolder set by
377          * {@link FullWidthDetailsOverviewRowPresenter#setState(ViewHolder, int)}.
378          */
getState()379         public final int getState() {
380             return mState;
381         }
382     }
383 
384     protected int mInitialState = STATE_HALF;
385 
386     private final Presenter mDetailsPresenter;
387     private final DetailsOverviewLogoPresenter mDetailsOverviewLogoPresenter;
388     private OnActionClickedListener mActionClickedListener;
389 
390     private int mBackgroundColor = Color.TRANSPARENT;
391     private int mActionsBackgroundColor = Color.TRANSPARENT;
392     private boolean mBackgroundColorSet;
393     private boolean mActionsBackgroundColorSet;
394 
395     private Listener mListener;
396     private boolean mParticipatingEntranceTransition;
397 
398     private int mAlignmentMode;
399 
400     /**
401      * Constructor for a FullWidthDetailsOverviewRowPresenter.
402      *
403      * @param detailsPresenter The {@link Presenter} used to render the detailed
404      *        description of the row.
405      */
FullWidthDetailsOverviewRowPresenter(Presenter detailsPresenter)406     public FullWidthDetailsOverviewRowPresenter(Presenter detailsPresenter) {
407         this(detailsPresenter, new DetailsOverviewLogoPresenter());
408     }
409 
410     /**
411      * Constructor for a FullWidthDetailsOverviewRowPresenter.
412      *
413      * @param detailsPresenter The {@link Presenter} used to render the detailed
414      *        description of the row.
415      * @param logoPresenter  The {@link Presenter} used to render the logo view.
416      */
FullWidthDetailsOverviewRowPresenter(Presenter detailsPresenter, DetailsOverviewLogoPresenter logoPresenter)417     public FullWidthDetailsOverviewRowPresenter(Presenter detailsPresenter,
418             DetailsOverviewLogoPresenter logoPresenter) {
419         setHeaderPresenter(null);
420         setSelectEffectEnabled(false);
421         mDetailsPresenter = detailsPresenter;
422         mDetailsOverviewLogoPresenter = logoPresenter;
423     }
424 
425     /**
426      * Sets the listener for Action click events.
427      */
setOnActionClickedListener(OnActionClickedListener listener)428     public void setOnActionClickedListener(OnActionClickedListener listener) {
429         mActionClickedListener = listener;
430     }
431 
432     /**
433      * Returns the listener for Action click events.
434      */
getOnActionClickedListener()435     public OnActionClickedListener getOnActionClickedListener() {
436         return mActionClickedListener;
437     }
438 
439     /**
440      * Sets the background color.  If not set, a default from the theme will be used.
441      */
setBackgroundColor(int color)442     public final void setBackgroundColor(int color) {
443         mBackgroundColor = color;
444         mBackgroundColorSet = true;
445     }
446 
447     /**
448      * Returns the background color.  If {@link #setBackgroundColor(int)}, transparent
449      * is returned.
450      */
getBackgroundColor()451     public final int getBackgroundColor() {
452         return mBackgroundColor;
453     }
454 
455     /**
456      * Sets the background color for Action Bar.  If not set, a default from the theme will be
457      * used.
458      */
setActionsBackgroundColor(int color)459     public final void setActionsBackgroundColor(int color) {
460         mActionsBackgroundColor = color;
461         mActionsBackgroundColorSet = true;
462     }
463 
464     /**
465      * Returns the background color of actions.  If {@link #setActionsBackgroundColor(int)}
466      * is not called,  transparent is returned.
467      */
getActionsBackgroundColor()468     public final int getActionsBackgroundColor() {
469         return mActionsBackgroundColor;
470     }
471 
472     /**
473      * Returns true if the overview should be part of shared element transition.
474      */
isParticipatingEntranceTransition()475     public final boolean isParticipatingEntranceTransition() {
476         return mParticipatingEntranceTransition;
477     }
478 
479     /**
480      * Sets if the overview should be part of shared element transition.
481      */
setParticipatingEntranceTransition(boolean participating)482     public final void setParticipatingEntranceTransition(boolean participating) {
483         mParticipatingEntranceTransition = participating;
484     }
485 
486     /**
487      * Change the initial state used to create ViewHolder.
488      */
setInitialState(int state)489     public final void setInitialState(int state) {
490         mInitialState = state;
491     }
492 
493     /**
494      * Returns the initial state used to create ViewHolder.
495      */
getInitialState()496     public final int getInitialState() {
497         return mInitialState;
498     }
499 
500     /**
501      * Set alignment mode of Description.
502      *
503      * @param alignmentMode  One of {@link #ALIGN_MODE_MIDDLE} or {@link #ALIGN_MODE_START}
504      */
setAlignmentMode(int alignmentMode)505     public final void setAlignmentMode(int alignmentMode) {
506         mAlignmentMode = alignmentMode;
507     }
508 
509     /**
510      * Returns alignment mode of Description.
511      *
512      * @return  One of {@link #ALIGN_MODE_MIDDLE} or {@link #ALIGN_MODE_START}.
513      */
getAlignmentMode()514     public final int getAlignmentMode() {
515         return mAlignmentMode;
516     }
517 
518     @Override
isClippingChildren()519     protected boolean isClippingChildren() {
520         return true;
521     }
522 
523     /**
524      * Set listener for details overview presenter. Must be called before creating
525      * ViewHolder.
526      */
setListener(Listener listener)527     public final void setListener(Listener listener) {
528         mListener = listener;
529     }
530 
531     /**
532      * Get resource id to inflate the layout.  The layout must match {@link #STATE_HALF}
533      */
getLayoutResourceId()534     protected int getLayoutResourceId() {
535         return R.layout.lb_fullwidth_details_overview;
536     }
537 
538     @Override
createRowViewHolder(ViewGroup parent)539     protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) {
540         View v = LayoutInflater.from(parent.getContext())
541             .inflate(getLayoutResourceId(), parent, false);
542         final ViewHolder vh = new ViewHolder(v, mDetailsPresenter, mDetailsOverviewLogoPresenter);
543         mDetailsOverviewLogoPresenter.setContext(vh.mDetailsLogoViewHolder, vh, this);
544         setState(vh, mInitialState);
545 
546         vh.mActionBridgeAdapter = new ActionsItemBridgeAdapter(vh);
547         final View overview = vh.mOverviewFrame;
548         if (mBackgroundColorSet) {
549             overview.setBackgroundColor(mBackgroundColor);
550         }
551         if (mActionsBackgroundColorSet) {
552             overview.findViewById(R.id.details_overview_actions_background)
553                     .setBackgroundColor(mActionsBackgroundColor);
554         }
555         RoundedRectHelper.getInstance().setClipToRoundedOutline(overview, true);
556 
557         if (!getSelectEffectEnabled()) {
558             vh.mOverviewFrame.setForeground(null);
559         }
560 
561         vh.mActionsRow.setOnUnhandledKeyListener(new BaseGridView.OnUnhandledKeyListener() {
562             @Override
563             public boolean onUnhandledKey(KeyEvent event) {
564                 if (vh.getOnKeyListener() != null) {
565                     if (vh.getOnKeyListener().onKey(vh.view, event.getKeyCode(), event)) {
566                         return true;
567                     }
568                 }
569                 return false;
570             }
571         });
572         return vh;
573     }
574 
getNonNegativeWidth(Drawable drawable)575     private static int getNonNegativeWidth(Drawable drawable) {
576         final int width = (drawable == null) ? 0 : drawable.getIntrinsicWidth();
577         return (width > 0 ? width : 0);
578     }
579 
getNonNegativeHeight(Drawable drawable)580     private static int getNonNegativeHeight(Drawable drawable) {
581         final int height = (drawable == null) ? 0 : drawable.getIntrinsicHeight();
582         return (height > 0 ? height : 0);
583     }
584 
585     @Override
onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item)586     protected void onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item) {
587         super.onBindRowViewHolder(holder, item);
588 
589         DetailsOverviewRow row = (DetailsOverviewRow) item;
590         ViewHolder vh = (ViewHolder) holder;
591 
592         mDetailsOverviewLogoPresenter.onBindViewHolder(vh.mDetailsLogoViewHolder, row);
593         mDetailsPresenter.onBindViewHolder(vh.mDetailsDescriptionViewHolder, row.getItem());
594         vh.onBind();
595     }
596 
597     @Override
onUnbindRowViewHolder(RowPresenter.ViewHolder holder)598     protected void onUnbindRowViewHolder(RowPresenter.ViewHolder holder) {
599         ViewHolder vh = (ViewHolder) holder;
600         vh.onUnbind();
601         mDetailsPresenter.onUnbindViewHolder(vh.mDetailsDescriptionViewHolder);
602         mDetailsOverviewLogoPresenter.onUnbindViewHolder(vh.mDetailsLogoViewHolder);
603         super.onUnbindRowViewHolder(holder);
604     }
605 
606     @Override
isUsingDefaultSelectEffect()607     public final boolean isUsingDefaultSelectEffect() {
608         return false;
609     }
610 
611     @Override
onSelectLevelChanged(RowPresenter.ViewHolder holder)612     protected void onSelectLevelChanged(RowPresenter.ViewHolder holder) {
613         super.onSelectLevelChanged(holder);
614         if (getSelectEffectEnabled()) {
615             ViewHolder vh = (ViewHolder) holder;
616             int dimmedColor = vh.mColorDimmer.getPaint().getColor();
617             ((ColorDrawable) vh.mOverviewFrame.getForeground().mutate()).setColor(dimmedColor);
618         }
619     }
620 
621     @Override
onRowViewAttachedToWindow(RowPresenter.ViewHolder vh)622     protected void onRowViewAttachedToWindow(RowPresenter.ViewHolder vh) {
623         super.onRowViewAttachedToWindow(vh);
624         ViewHolder viewHolder = (ViewHolder) vh;
625         mDetailsPresenter.onViewAttachedToWindow(viewHolder.mDetailsDescriptionViewHolder);
626         mDetailsOverviewLogoPresenter.onViewAttachedToWindow(viewHolder.mDetailsLogoViewHolder);
627     }
628 
629     @Override
onRowViewDetachedFromWindow(RowPresenter.ViewHolder vh)630     protected void onRowViewDetachedFromWindow(RowPresenter.ViewHolder vh) {
631         super.onRowViewDetachedFromWindow(vh);
632         ViewHolder viewHolder = (ViewHolder) vh;
633         mDetailsPresenter.onViewDetachedFromWindow(viewHolder.mDetailsDescriptionViewHolder);
634         mDetailsOverviewLogoPresenter.onViewDetachedFromWindow(viewHolder.mDetailsLogoViewHolder);
635     }
636 
637     /**
638      * Called by {@link DetailsOverviewLogoPresenter} to notify logo was bound to view.
639      * Application should not directly call this method.
640      * @param viewHolder  The row ViewHolder that has logo bound to view.
641      */
notifyOnBindLogo(ViewHolder viewHolder)642     public final void notifyOnBindLogo(ViewHolder viewHolder) {
643         onLayoutOverviewFrame(viewHolder, viewHolder.getState(), true);
644         onLayoutLogo(viewHolder, viewHolder.getState(), true);
645         if (mListener != null) {
646             mListener.onBindLogo(viewHolder);
647         }
648     }
649 
650     /**
651      * Layout logo position based on current state.  Subclass may override.
652      * The method is called when a logo is bound to view or state changes.
653      * @param viewHolder  The row ViewHolder that contains the logo.
654      * @param oldState    The old state,  can be same as current viewHolder.getState()
655      * @param logoChanged Whether logo was changed.
656      */
onLayoutLogo(ViewHolder viewHolder, int oldState, boolean logoChanged)657     protected void onLayoutLogo(ViewHolder viewHolder, int oldState, boolean logoChanged) {
658         View v = viewHolder.getLogoViewHolder().view;
659         ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams)
660                 v.getLayoutParams();
661         switch (mAlignmentMode) {
662             case ALIGN_MODE_START:
663             default:
664                 lp.setMarginStart(v.getResources().getDimensionPixelSize(
665                         R.dimen.lb_details_v2_logo_margin_start));
666                 break;
667             case ALIGN_MODE_MIDDLE:
668                 lp.setMarginStart(v.getResources().getDimensionPixelSize(R.dimen.lb_details_v2_left)
669                         - lp.width);
670                 break;
671         }
672 
673         switch (viewHolder.getState()) {
674         case STATE_FULL:
675         default:
676             lp.topMargin =
677                     v.getResources().getDimensionPixelSize(R.dimen.lb_details_v2_blank_height)
678                     - lp.height / 2;
679             break;
680         case STATE_HALF:
681             lp.topMargin = v.getResources().getDimensionPixelSize(
682                     R.dimen.lb_details_v2_blank_height) + v.getResources()
683                     .getDimensionPixelSize(R.dimen.lb_details_v2_actions_height) + v
684                     .getResources().getDimensionPixelSize(
685                     R.dimen.lb_details_v2_description_margin_top);
686             break;
687         case STATE_SMALL:
688             lp.topMargin = 0;
689             break;
690         }
691         v.setLayoutParams(lp);
692     }
693 
694     /**
695      * Layout overview frame based on current state.  Subclass may override.
696      * The method is called when a logo is bound to view or state changes.
697      * @param viewHolder  The row ViewHolder that contains the logo.
698      * @param oldState    The old state,  can be same as current viewHolder.getState()
699      * @param logoChanged Whether logo was changed.
700      */
onLayoutOverviewFrame(ViewHolder viewHolder, int oldState, boolean logoChanged)701     protected void onLayoutOverviewFrame(ViewHolder viewHolder, int oldState, boolean logoChanged) {
702         boolean wasBanner = oldState == STATE_SMALL;
703         boolean isBanner = viewHolder.getState() == STATE_SMALL;
704         if (wasBanner != isBanner || logoChanged) {
705             Resources res = viewHolder.view.getResources();
706 
707             int frameMarginStart;
708             int descriptionMarginStart = 0;
709             int logoWidth = 0;
710             if (mDetailsOverviewLogoPresenter.isBoundToImage(viewHolder.getLogoViewHolder(),
711                     (DetailsOverviewRow) viewHolder.getRow())) {
712                 logoWidth = viewHolder.getLogoViewHolder().view.getLayoutParams().width;
713             }
714             switch (mAlignmentMode) {
715                 case ALIGN_MODE_START:
716                 default:
717                     if (isBanner) {
718                         frameMarginStart = res.getDimensionPixelSize(
719                                 R.dimen.lb_details_v2_logo_margin_start);
720                         descriptionMarginStart = logoWidth;
721                     } else {
722                         frameMarginStart = 0;
723                         descriptionMarginStart = logoWidth + res.getDimensionPixelSize(
724                                 R.dimen.lb_details_v2_logo_margin_start);
725                     }
726                     break;
727                 case ALIGN_MODE_MIDDLE:
728                     if (isBanner) {
729                         frameMarginStart = res.getDimensionPixelSize(R.dimen.lb_details_v2_left)
730                                 - logoWidth;
731                         descriptionMarginStart = logoWidth;
732                     } else {
733                         frameMarginStart = 0;
734                         descriptionMarginStart = res.getDimensionPixelSize(
735                                 R.dimen.lb_details_v2_left);
736                     }
737                     break;
738             }
739             MarginLayoutParams lpFrame =
740                     (MarginLayoutParams) viewHolder.getOverviewView().getLayoutParams();
741             lpFrame.topMargin = isBanner ? 0
742                     : res.getDimensionPixelSize(R.dimen.lb_details_v2_blank_height);
743             lpFrame.leftMargin = lpFrame.rightMargin = frameMarginStart;
744             viewHolder.getOverviewView().setLayoutParams(lpFrame);
745 
746             View description = viewHolder.getDetailsDescriptionFrame();
747             MarginLayoutParams lpDesc = (MarginLayoutParams) description.getLayoutParams();
748             lpDesc.setMarginStart(descriptionMarginStart);
749             description.setLayoutParams(lpDesc);
750 
751             View action = viewHolder.getActionsRow();
752             MarginLayoutParams lpActions = (MarginLayoutParams) action.getLayoutParams();
753             lpActions.setMarginStart(descriptionMarginStart);
754             lpActions.height =
755                     isBanner ? 0 : res.getDimensionPixelSize(R.dimen.lb_details_v2_actions_height);
756             action.setLayoutParams(lpActions);
757         }
758     }
759 
760     /**
761      * Switch state of a ViewHolder.
762      * @param viewHolder   The ViewHolder to change state.
763      * @param state        New state, can be {@link #STATE_FULL}, {@link #STATE_HALF}
764      *                     or {@link #STATE_SMALL}.
765      */
setState(ViewHolder viewHolder, int state)766     public final void setState(ViewHolder viewHolder, int state) {
767         if (viewHolder.getState() != state) {
768             int oldState = viewHolder.getState();
769             viewHolder.mState = state;
770             onStateChanged(viewHolder, oldState);
771         }
772     }
773 
774     /**
775      * Called when {@link ViewHolder#getState()} changes.  Subclass may override.
776      * The default implementation calls {@link #onLayoutLogo(ViewHolder, int, boolean)} and
777      * {@link #onLayoutOverviewFrame(ViewHolder, int, boolean)}.
778      * @param viewHolder   The ViewHolder which state changed.
779      * @param oldState     The old state.
780      */
onStateChanged(ViewHolder viewHolder, int oldState)781     protected void onStateChanged(ViewHolder viewHolder, int oldState) {
782         onLayoutOverviewFrame(viewHolder, oldState, false);
783         onLayoutLogo(viewHolder, oldState, false);
784     }
785 
786     @Override
setEntranceTransitionState(RowPresenter.ViewHolder holder, boolean afterEntrance)787     public void setEntranceTransitionState(RowPresenter.ViewHolder holder,
788             boolean afterEntrance) {
789         super.setEntranceTransitionState(holder, afterEntrance);
790         if (mParticipatingEntranceTransition) {
791             holder.view.setVisibility(afterEntrance? View.VISIBLE : View.INVISIBLE);
792         }
793     }
794 }
795