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