Home | History | Annotate | Download | only in widget
      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          */
    107         public void onBindLogo(ViewHolder vh) {
    108         }
    109 
    110     }
    111 
    112     class ActionsItemBridgeAdapter extends ItemBridgeAdapter {
    113         FullWidthDetailsOverviewRowPresenter.ViewHolder mViewHolder;
    114 
    115         ActionsItemBridgeAdapter(FullWidthDetailsOverviewRowPresenter.ViewHolder viewHolder) {
    116             mViewHolder = viewHolder;
    117         }
    118 
    119         @Override
    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
    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
    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
    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 
    166         protected DetailsOverviewRow.Listener createRowListener() {
    167             return new DetailsOverviewRowListener();
    168         }
    169 
    170         public class DetailsOverviewRowListener extends DetailsOverviewRow.Listener {
    171             @Override
    172             public void onImageDrawableChanged(DetailsOverviewRow row) {
    173                 sHandler.removeCallbacks(mUpdateDrawableCallback);
    174                 sHandler.post(mUpdateDrawableCallback);
    175             }
    176 
    177             @Override
    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
    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 
    212         void bindActions(ObjectAdapter adapter) {
    213             mActionBridgeAdapter.setAdapter(adapter);
    214             mActionsRow.setAdapter(mActionBridgeAdapter);
    215             mNumItems = mActionBridgeAdapter.getItemCount();
    216 
    217         }
    218 
    219         void onBind() {
    220             DetailsOverviewRow row = (DetailsOverviewRow) getRow();
    221             bindActions(row.getActionsAdapter());
    222             row.addListener(mRowListener);
    223         }
    224 
    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 
    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 
    281         private int getViewCenter(View view) {
    282             return (view.getRight() - view.getLeft()) / 2;
    283         }
    284 
    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          */
    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          */
    337         public final ViewGroup getOverviewView() {
    338             return mOverviewFrame;
    339         }
    340 
    341         /**
    342          * Returns the ViewHolder for logo.
    343          */
    344         public final DetailsOverviewLogoPresenter.ViewHolder getLogoViewHolder() {
    345             return mDetailsLogoViewHolder;
    346         }
    347 
    348         /**
    349          * Returns the ViewHolder for DetailsDescription.
    350          */
    351         public final Presenter.ViewHolder getDetailsDescriptionViewHolder() {
    352             return mDetailsDescriptionViewHolder;
    353         }
    354 
    355         /**
    356          * Returns the root view for inserting details description.
    357          */
    358         public final ViewGroup getDetailsDescriptionFrame() {
    359             return mDetailsDescriptionFrame;
    360         }
    361 
    362         /**
    363          * Returns the view of actions row.
    364          */
    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          */
    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      */
    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      */
    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      */
    422     public void setOnActionClickedListener(OnActionClickedListener listener) {
    423         mActionClickedListener = listener;
    424     }
    425 
    426     /**
    427      * Returns the listener for Action click events.
    428      */
    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      */
    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      */
    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      */
    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      */
    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      */
    469     public final boolean isParticipatingEntranceTransition() {
    470         return mParticipatingEntranceTransition;
    471     }
    472 
    473     /**
    474      * Sets if the overview should be part of shared element transition.
    475      */
    476     public final void setParticipatingEntranceTransition(boolean participating) {
    477         mParticipatingEntranceTransition = participating;
    478     }
    479 
    480     /**
    481      * Change the initial state used to create ViewHolder.
    482      */
    483     public final void setInitialState(int state) {
    484         mInitialState = state;
    485     }
    486 
    487     /**
    488      * Returns the initial state used to create ViewHolder.
    489      */
    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      */
    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      */
    508     public final int getAlignmentMode() {
    509         return mAlignmentMode;
    510     }
    511 
    512     @Override
    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      */
    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      */
    528     protected int getLayoutResourceId() {
    529         return R.layout.lb_fullwidth_details_overview;
    530     }
    531 
    532     @Override
    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 
    569     private static int getNonNegativeWidth(Drawable drawable) {
    570         final int width = (drawable == null) ? 0 : drawable.getIntrinsicWidth();
    571         return (width > 0 ? width : 0);
    572     }
    573 
    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
    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
    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
    601     public final boolean isUsingDefaultSelectEffect() {
    602         return false;
    603     }
    604 
    605     @Override
    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
    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
    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      */
    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      */
    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      */
    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      */
    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      */
    775     protected void onStateChanged(ViewHolder viewHolder, int oldState) {
    776         onLayoutOverviewFrame(viewHolder, oldState, false);
    777         onLayoutLogo(viewHolder, oldState, false);
    778     }
    779 
    780     @Override
    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