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