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