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