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