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