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