BaseCardView.java revision 961a8f12d1fbeeb86ea066f9f2c693abb2ce50f2
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.support.v17.leanback.widget;
18
19import android.content.Context;
20import android.content.res.TypedArray;
21import android.support.v17.leanback.R;
22import android.util.AttributeSet;
23import android.util.Log;
24import android.view.View;
25import android.view.ViewDebug;
26import android.view.ViewGroup;
27import android.view.animation.AccelerateDecelerateInterpolator;
28import android.view.animation.Animation;
29import android.view.animation.DecelerateInterpolator;
30import android.view.animation.Transformation;
31
32import java.util.ArrayList;
33
34/**
35 * A card style layout that responds to certain state changes. It arranges its
36 * children in a vertical column, with different regions becoming visible at
37 * different times.
38 *
39 * <p>
40 * A BaseCardView will draw its children based on its type, the region
41 * visibilities of the child types, and the state of the widget. A child may be
42 * marked as belonging to one of three regions: main, info, or extra. The main
43 * region is always visible, while the info and extra regions can be set to
44 * display based on the activated or selected state of the View. The card states
45 * are set by calling {@link #setActivated(boolean) setActivated} and
46 * {@link #setSelected(boolean) setSelected}.
47 * <p>
48 * See {@link BaseCardView.LayoutParams} for layout attributes.
49 * </p>
50 */
51public class BaseCardView extends ViewGroup {
52    private static final String TAG = "BaseCardView";
53    private static final boolean DEBUG = false;
54
55    /**
56     * A simple card type with a single layout area. This card type does not
57     * change its layout or size as it transitions between
58     * Activated/Not-Activated or Selected/Unselected states.
59     *
60     * @see #getCardType()
61     */
62    public static final int CARD_TYPE_MAIN_ONLY = 0;
63
64    /**
65     * A Card type with 2 layout areas: A main area which is always visible, and
66     * an info area that fades in over the main area when it is visible.
67     * The card height will not change.
68     *
69     * @see #getCardType()
70     */
71    public static final int CARD_TYPE_INFO_OVER = 1;
72
73    /**
74     * A Card type with 2 layout areas: A main area which is always visible, and
75     * an info area that appears below the main area. When the info area is visible
76     * the total card height will change.
77     *
78     * @see #getCardType()
79     */
80    public static final int CARD_TYPE_INFO_UNDER = 2;
81
82    /**
83     * A Card type with 3 layout areas: A main area which is always visible; an
84     * info area which will appear below the main area, and an extra area that
85     * only appears after a short delay. The info area appears below the main
86     * area, causing the total card height to change. The extra area animates in
87     * at the bottom of the card, shifting up the info view without affecting
88     * the card height.
89     *
90     * @see #getCardType()
91     */
92    public static final int CARD_TYPE_INFO_UNDER_WITH_EXTRA = 3;
93
94    /**
95     * Indicates that a card region is always visible.
96     */
97    public static final int CARD_REGION_VISIBLE_ALWAYS = 0;
98
99    /**
100     * Indicates that a card region is visible when the card is activated.
101     */
102    public static final int CARD_REGION_VISIBLE_ACTIVATED = 1;
103
104    /**
105     * Indicates that a card region is visible when the card is selected.
106     */
107    public static final int CARD_REGION_VISIBLE_SELECTED = 2;
108
109    private static final int CARD_TYPE_INVALID = 4;
110
111    private int mCardType;
112    private int mInfoVisibility;
113    private int mExtraVisibility;
114
115    private ArrayList<View> mMainViewList;
116    private ArrayList<View> mInfoViewList;
117    private ArrayList<View> mExtraViewList;
118
119    private int mMeasuredWidth;
120    private int mMeasuredHeight;
121    private boolean mDelaySelectedAnim;
122    private int mSelectedAnimationDelay;
123    private final int mActivatedAnimDuration;
124    private final int mSelectedAnimDuration;
125
126    private float mInfoOffset;
127    private float mInfoVisFraction;
128    private float mInfoAlpha = 1.0f;
129    private Animation mAnim;
130
131    private final Runnable mAnimationTrigger = new Runnable() {
132        @Override
133        public void run() {
134            animateInfoOffset(true);
135        }
136    };
137
138    public BaseCardView(Context context) {
139        this(context, null);
140    }
141
142    public BaseCardView(Context context, AttributeSet attrs) {
143        this(context, attrs, R.attr.baseCardViewStyle);
144    }
145
146    public BaseCardView(Context context, AttributeSet attrs, int defStyle) {
147        super(context, attrs, defStyle);
148
149        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbBaseCardView, defStyle, 0);
150
151        try {
152            mCardType = a.getInteger(R.styleable.lbBaseCardView_cardType, CARD_TYPE_MAIN_ONLY);
153            mInfoVisibility = a.getInteger(R.styleable.lbBaseCardView_infoVisibility,
154                    CARD_REGION_VISIBLE_ACTIVATED);
155            mExtraVisibility = a.getInteger(R.styleable.lbBaseCardView_extraVisibility,
156                    CARD_REGION_VISIBLE_SELECTED);
157            // Extra region should never show before info region.
158            if (mExtraVisibility < mInfoVisibility) {
159                mExtraVisibility = mInfoVisibility;
160            }
161
162            mSelectedAnimationDelay = a.getInteger(
163                    R.styleable.lbBaseCardView_selectedAnimationDelay,
164                    getResources().getInteger(R.integer.lb_card_selected_animation_delay));
165
166            mSelectedAnimDuration = a.getInteger(
167                    R.styleable.lbBaseCardView_selectedAnimationDuration,
168                    getResources().getInteger(R.integer.lb_card_selected_animation_duration));
169
170            mActivatedAnimDuration =
171                    a.getInteger(R.styleable.lbBaseCardView_activatedAnimationDuration,
172                    getResources().getInteger(R.integer.lb_card_activated_animation_duration));
173        } finally {
174            a.recycle();
175        }
176
177        mDelaySelectedAnim = true;
178
179        mMainViewList = new ArrayList<View>();
180        mInfoViewList = new ArrayList<View>();
181        mExtraViewList = new ArrayList<View>();
182
183        mInfoOffset = 0.0f;
184        mInfoVisFraction = 0.0f;
185    }
186
187    /**
188     * Sets a flag indicating if the Selected animation (if the selected card
189     * type implements one) should run immediately after the card is selected,
190     * or if it should be delayed. The default behavior is to delay this
191     * animation. This is a one-shot override. If set to false, after the card
192     * is selected and the selected animation is triggered, this flag is
193     * automatically reset to true. This is useful when you want to change the
194     * default behavior, and have the selected animation run immediately. One
195     * such case could be when focus moves from one row to the other, when
196     * instead of delaying the selected animation until the user pauses on a
197     * card, it may be desirable to trigger the animation for that card
198     * immediately.
199     *
200     * @param delay True (default) if the selected animation should be delayed
201     *            after the card is selected, or false if the animation should
202     *            run immediately the next time the card is Selected.
203     */
204    public void setSelectedAnimationDelayed(boolean delay) {
205        mDelaySelectedAnim = delay;
206    }
207
208    /**
209     * Returns a boolean indicating if the selected animation will run
210     * immediately or be delayed the next time the card is Selected.
211     *
212     * @return true if this card is set to delay the selected animation the next
213     *         time it is selected, or false if the selected animation will run
214     *         immediately the next time the card is selected.
215     */
216    public boolean isSelectedAnimationDelayed() {
217        return mDelaySelectedAnim;
218    }
219
220    /**
221     * Sets the type of this Card.
222     *
223     * @param type The desired card type.
224     */
225    public void setCardType(int type) {
226        if (mCardType != type) {
227            if (type >= CARD_TYPE_MAIN_ONLY && type < CARD_TYPE_INVALID) {
228                // Valid card type
229                mCardType = type;
230            } else {
231                Log.e(TAG, "Invalid card type specified: " + type +
232                        ". Defaulting to type CARD_TYPE_MAIN_ONLY.");
233                mCardType = CARD_TYPE_MAIN_ONLY;
234            }
235            requestLayout();
236        }
237    }
238
239    /**
240     * Returns the type of this Card.
241     *
242     * @return The type of this card.
243     */
244    public int getCardType() {
245        return mCardType;
246    }
247
248    public void setInfoVisibility(int visibility) {
249        if (mInfoVisibility != visibility) {
250            mInfoVisibility = visibility;
251            if (mInfoVisibility == CARD_REGION_VISIBLE_SELECTED && isSelected()) {
252                mInfoVisFraction = 1.0f;
253            } else {
254                mInfoVisFraction = 0.0f;
255            }
256            requestLayout();
257        }
258    }
259
260    public int getInfoVisibility() {
261        return mInfoVisibility;
262    }
263
264    public void setExtraVisibility(int visibility) {
265        if (mExtraVisibility != visibility) {
266            mExtraVisibility = visibility;
267            requestLayout();
268        }
269    }
270
271    public int getExtraVisibility() {
272        return mExtraVisibility;
273    }
274
275    /**
276     * Sets the Activated state of this Card. This can trigger changes in the
277     * card layout, resulting in views to become visible or hidden. A card is
278     * normally set to Activated state when its parent container (like a Row)
279     * receives focus, and then activates all of its children.
280     *
281     * @param activated True if the card is ACTIVE, or false if INACTIVE.
282     * @see #isActivated()
283     */
284    @Override
285    public void setActivated(boolean activated) {
286        if (activated != isActivated()) {
287            super.setActivated(activated);
288            applyActiveState(isActivated());
289        }
290    }
291
292    /**
293     * Sets the Selected state of this Card. This can trigger changes in the
294     * card layout, resulting in views to become visible or hidden. A card is
295     * normally set to Selected state when it receives input focus.
296     *
297     * @param selected True if the card is Selected, or false otherwise.
298     * @see #isSelected()
299     */
300    @Override
301    public void setSelected(boolean selected) {
302        if (selected != isSelected()) {
303            super.setSelected(selected);
304            applySelectedState(isSelected());
305        }
306    }
307
308    @Override
309    public boolean shouldDelayChildPressedState() {
310        return false;
311    }
312
313    @Override
314    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
315        mMeasuredWidth = 0;
316        mMeasuredHeight = 0;
317        int state = 0;
318        int mainHeight = 0;
319        int infoHeight = 0;
320        int extraHeight = 0;
321
322        findChildrenViews();
323
324        final int unspecifiedSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
325        // MAIN is always present
326        for (int i = 0; i < mMainViewList.size(); i++) {
327            View mainView = mMainViewList.get(i);
328            if (mainView.getVisibility() != View.GONE) {
329                measureChild(mainView, unspecifiedSpec, unspecifiedSpec);
330                mMeasuredWidth = Math.max(mMeasuredWidth, mainView.getMeasuredWidth());
331                mainHeight += mainView.getMeasuredHeight();
332                state = View.combineMeasuredStates(state, mainView.getMeasuredState());
333            }
334        }
335        setPivotX(mMeasuredWidth / 2);
336        setPivotY(mainHeight / 2);
337
338
339        // The MAIN area determines the card width
340        int cardWidthMeasureSpec = MeasureSpec.makeMeasureSpec(mMeasuredWidth, MeasureSpec.EXACTLY);
341
342        if (hasInfoRegion()) {
343            for (int i = 0; i < mInfoViewList.size(); i++) {
344                View infoView = mInfoViewList.get(i);
345                if (infoView.getVisibility() != View.GONE) {
346                    measureChild(infoView, cardWidthMeasureSpec, unspecifiedSpec);
347                    if (mCardType != CARD_TYPE_INFO_OVER) {
348                        infoHeight += infoView.getMeasuredHeight();
349                    }
350                    state = View.combineMeasuredStates(state, infoView.getMeasuredState());
351                }
352            }
353
354            if (hasExtraRegion()) {
355                for (int i = 0; i < mExtraViewList.size(); i++) {
356                    View extraView = mExtraViewList.get(i);
357                    if (extraView.getVisibility() != View.GONE) {
358                        measureChild(extraView, cardWidthMeasureSpec, unspecifiedSpec);
359                        extraHeight += extraView.getMeasuredHeight();
360                        state = View.combineMeasuredStates(state, extraView.getMeasuredState());
361                    }
362                }
363            }
364        }
365
366        boolean infoAnimating = hasInfoRegion() && mInfoVisibility == CARD_REGION_VISIBLE_SELECTED;
367        mMeasuredHeight = (int) (mainHeight +
368                (infoAnimating ? (infoHeight * mInfoVisFraction) : infoHeight)
369                + extraHeight - (infoAnimating ? 0 : mInfoOffset));
370
371        // Report our final dimensions.
372        setMeasuredDimension(View.resolveSizeAndState(mMeasuredWidth + getPaddingLeft() +
373                getPaddingRight(), widthMeasureSpec, state),
374                View.resolveSizeAndState(mMeasuredHeight + getPaddingTop() + getPaddingBottom(),
375                        heightMeasureSpec, state << View.MEASURED_HEIGHT_STATE_SHIFT));
376    }
377
378    @Override
379    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
380        float currBottom = getPaddingTop();
381
382        // MAIN is always present
383        for (int i = 0; i < mMainViewList.size(); i++) {
384            View mainView = mMainViewList.get(i);
385            if (mainView.getVisibility() != View.GONE) {
386                mainView.layout(getPaddingLeft(),
387                        (int) currBottom,
388                                mMeasuredWidth + getPaddingLeft(),
389                        (int) (currBottom + mainView.getMeasuredHeight()));
390                currBottom += mainView.getMeasuredHeight();
391            }
392        }
393
394        if (hasInfoRegion()) {
395            float infoHeight = 0f;
396            for (int i = 0; i < mInfoViewList.size(); i++) {
397                infoHeight += mInfoViewList.get(i).getMeasuredHeight();
398            }
399
400            if (mCardType == CARD_TYPE_INFO_OVER) {
401                // retract currBottom to overlap the info views on top of main
402                currBottom -= infoHeight;
403                if (currBottom < 0) {
404                    currBottom = 0;
405                }
406            } else if (mCardType == CARD_TYPE_INFO_UNDER) {
407                if (mInfoVisibility == CARD_REGION_VISIBLE_SELECTED) {
408                    infoHeight = infoHeight * mInfoVisFraction;
409                }
410            } else {
411                currBottom -= mInfoOffset;
412            }
413
414            for (int i = 0; i < mInfoViewList.size(); i++) {
415                View infoView = mInfoViewList.get(i);
416                if (infoView.getVisibility() != View.GONE) {
417                    int viewHeight = infoView.getMeasuredHeight();
418                    if (viewHeight > infoHeight) {
419                        viewHeight = (int) infoHeight;
420                    }
421                    infoView.layout(getPaddingLeft(),
422                            (int) currBottom,
423                                    mMeasuredWidth + getPaddingLeft(),
424                            (int) (currBottom + viewHeight));
425                    currBottom += viewHeight;
426                    infoHeight -= viewHeight;
427                    if (infoHeight <= 0) {
428                        break;
429                    }
430                }
431            }
432
433            if (hasExtraRegion()) {
434                for (int i = 0; i < mExtraViewList.size(); i++) {
435                    View extraView = mExtraViewList.get(i);
436                    if (extraView.getVisibility() != View.GONE) {
437                        extraView.layout(getPaddingLeft(),
438                                (int) currBottom,
439                                        mMeasuredWidth + getPaddingLeft(),
440                                (int) (currBottom + extraView.getMeasuredHeight()));
441                        currBottom += extraView.getMeasuredHeight();
442                    }
443                }
444            }
445        }
446    }
447
448    @Override
449    protected void onDetachedFromWindow() {
450        super.onDetachedFromWindow();
451        removeCallbacks(mAnimationTrigger);
452        cancelAnimations();
453        mInfoOffset = 0.0f;
454        mInfoVisFraction = 0.0f;
455    }
456
457    private boolean hasInfoRegion() {
458        return mCardType != CARD_TYPE_MAIN_ONLY;
459    }
460
461    private boolean hasExtraRegion() {
462        return mCardType == CARD_TYPE_INFO_UNDER_WITH_EXTRA;
463    }
464
465    private boolean isRegionVisible(int regionVisibility) {
466        switch (regionVisibility) {
467            case CARD_REGION_VISIBLE_ALWAYS:
468                return true;
469            case CARD_REGION_VISIBLE_ACTIVATED:
470                return isActivated();
471            case CARD_REGION_VISIBLE_SELECTED:
472                return isActivated() && isSelected();
473            default:
474                if (DEBUG) Log.e(TAG, "invalid region visibility state: " + regionVisibility);
475                return false;
476        }
477    }
478
479    private void findChildrenViews() {
480        mMainViewList.clear();
481        mInfoViewList.clear();
482        mExtraViewList.clear();
483
484        final int count = getChildCount();
485
486        boolean infoVisible = isRegionVisible(mInfoVisibility);
487        boolean extraVisible = hasExtraRegion() && mInfoOffset > 0f;
488
489        if (mCardType == CARD_TYPE_INFO_UNDER && mInfoVisibility == CARD_REGION_VISIBLE_SELECTED) {
490            infoVisible = infoVisible && mInfoVisFraction > 0f;
491        }
492
493        for (int i = 0; i < count; i++) {
494            final View child = getChildAt(i);
495
496            if (child == null) {
497                continue;
498            }
499
500            BaseCardView.LayoutParams lp = (BaseCardView.LayoutParams) child
501                    .getLayoutParams();
502            if (lp.viewType == LayoutParams.VIEW_TYPE_INFO) {
503                mInfoViewList.add(child);
504                child.setVisibility(infoVisible ? View.VISIBLE : View.GONE);
505            } else if (lp.viewType == LayoutParams.VIEW_TYPE_EXTRA) {
506                mExtraViewList.add(child);
507                child.setVisibility(extraVisible ? View.VISIBLE : View.GONE);
508            } else {
509                // Default to MAIN
510                mMainViewList.add(child);
511                child.setVisibility(View.VISIBLE);
512            }
513        }
514
515    }
516
517    private void applyActiveState(boolean active) {
518        if (hasInfoRegion() && mInfoVisibility <= CARD_REGION_VISIBLE_ACTIVATED) {
519            setInfoViewVisibility(active);
520        }
521        if (hasExtraRegion() && mExtraVisibility <= CARD_REGION_VISIBLE_ACTIVATED) {
522            //setExtraVisibility(active);
523        }
524    }
525
526    private void setInfoViewVisibility(boolean visible) {
527        if (mCardType == CARD_TYPE_INFO_UNDER_WITH_EXTRA) {
528            // Active state changes for card type
529            // CARD_TYPE_INFO_UNDER_WITH_EXTRA
530            if (visible) {
531                for (int i = 0; i < mInfoViewList.size(); i++) {
532                    mInfoViewList.get(i).setVisibility(View.VISIBLE);
533                }
534            } else {
535                for (int i = 0; i < mInfoViewList.size(); i++) {
536                    mInfoViewList.get(i).setVisibility(View.GONE);
537                }
538                for (int i = 0; i < mExtraViewList.size(); i++) {
539                    mExtraViewList.get(i).setVisibility(View.GONE);
540                }
541                mInfoOffset = 0.0f;
542            }
543        } else if (mCardType == CARD_TYPE_INFO_UNDER) {
544            // Active state changes for card type CARD_TYPE_INFO_UNDER
545            if (mInfoVisibility == CARD_REGION_VISIBLE_SELECTED) {
546                animateInfoHeight(visible);
547            } else {
548                for (int i = 0; i < mInfoViewList.size(); i++) {
549                    mInfoViewList.get(i).setVisibility(visible ? View.VISIBLE : View.GONE);
550                }
551            }
552        } else if (mCardType == CARD_TYPE_INFO_OVER) {
553            // Active state changes for card type CARD_TYPE_INFO_OVER
554            animateInfoAlpha(visible);
555        }
556    }
557
558    private void applySelectedState(boolean focused) {
559        removeCallbacks(mAnimationTrigger);
560
561        if (mCardType == CARD_TYPE_INFO_UNDER_WITH_EXTRA) {
562            // Focus changes for card type CARD_TYPE_INFO_UNDER_WITH_EXTRA
563            if (focused) {
564                if (!mDelaySelectedAnim) {
565                    post(mAnimationTrigger);
566                    mDelaySelectedAnim = true;
567                } else {
568                    postDelayed(mAnimationTrigger, mSelectedAnimationDelay);
569                }
570            } else {
571                animateInfoOffset(false);
572            }
573        } else if (mInfoVisibility == CARD_REGION_VISIBLE_SELECTED) {
574            setInfoViewVisibility(focused);
575        }
576    }
577
578    private void cancelAnimations() {
579        if (mAnim != null) {
580            mAnim.cancel();
581            mAnim = null;
582        }
583    }
584
585    // This animation changes the Y offset of the info and extra views,
586    // so that they animate UP to make the extra info area visible when a
587    // card is selected.
588    private void animateInfoOffset(boolean shown) {
589        cancelAnimations();
590
591        int extraHeight = 0;
592        if (shown) {
593            int widthSpec = MeasureSpec.makeMeasureSpec(mMeasuredWidth, MeasureSpec.EXACTLY);
594            int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
595
596            for (int i = 0; i < mExtraViewList.size(); i++) {
597                View extraView = mExtraViewList.get(i);
598                extraView.setVisibility(View.VISIBLE);
599                extraView.measure(widthSpec, heightSpec);
600                extraHeight = Math.max(extraHeight, extraView.getMeasuredHeight());
601            }
602        }
603
604        mAnim = new InfoOffsetAnimation(mInfoOffset, shown ? extraHeight : 0);
605        mAnim.setDuration(mSelectedAnimDuration);
606        mAnim.setInterpolator(new AccelerateDecelerateInterpolator());
607        mAnim.setAnimationListener(new Animation.AnimationListener() {
608            @Override
609            public void onAnimationStart(Animation animation) {
610            }
611
612            @Override
613            public void onAnimationEnd(Animation animation) {
614                if (mInfoOffset == 0f) {
615                    for (int i = 0; i < mExtraViewList.size(); i++) {
616                        mExtraViewList.get(i).setVisibility(View.GONE);
617                    }
618                }
619            }
620
621                @Override
622            public void onAnimationRepeat(Animation animation) {
623            }
624
625        });
626        startAnimation(mAnim);
627    }
628
629    // This animation changes the visible height of the info views,
630    // so that they animate in and out of view.
631    private void animateInfoHeight(boolean shown) {
632        cancelAnimations();
633
634        int extraHeight = 0;
635        if (shown) {
636            int widthSpec = MeasureSpec.makeMeasureSpec(mMeasuredWidth, MeasureSpec.EXACTLY);
637            int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
638
639            for (int i = 0; i < mExtraViewList.size(); i++) {
640                View extraView = mExtraViewList.get(i);
641                extraView.setVisibility(View.VISIBLE);
642                extraView.measure(widthSpec, heightSpec);
643                extraHeight = Math.max(extraHeight, extraView.getMeasuredHeight());
644            }
645        }
646
647        mAnim = new InfoHeightAnimation(mInfoVisFraction, shown ? 1.0f : 0f);
648        mAnim.setDuration(mSelectedAnimDuration);
649        mAnim.setInterpolator(new AccelerateDecelerateInterpolator());
650        mAnim.setAnimationListener(new Animation.AnimationListener() {
651                @Override
652            public void onAnimationStart(Animation animation) {
653            }
654
655                @Override
656            public void onAnimationEnd(Animation animation) {
657                if (mInfoOffset == 0f) {
658                    for (int i = 0; i < mExtraViewList.size(); i++) {
659                        mExtraViewList.get(i).setVisibility(View.GONE);
660                    }
661                }
662            }
663
664            @Override
665            public void onAnimationRepeat(Animation animation) {
666            }
667
668        });
669        startAnimation(mAnim);
670    }
671
672    // This animation changes the alpha of the info views, so they animate in
673    // and out. It's meant to be used when the info views are overlaid on top of
674    // the main view area. It gets triggered by a change in the Active state of
675    // the card.
676    private void animateInfoAlpha(boolean shown) {
677        cancelAnimations();
678
679        if (shown) {
680            for (int i = 0; i < mInfoViewList.size(); i++) {
681                mInfoViewList.get(i).setVisibility(View.VISIBLE);
682            }
683        }
684
685        mAnim = new InfoAlphaAnimation(mInfoAlpha, shown ? 1.0f : 0.0f);
686        mAnim.setDuration(mActivatedAnimDuration);
687        mAnim.setInterpolator(new DecelerateInterpolator());
688        mAnim.setAnimationListener(new Animation.AnimationListener() {
689            @Override
690            public void onAnimationStart(Animation animation) {
691            }
692
693            @Override
694            public void onAnimationEnd(Animation animation) {
695                if (mInfoAlpha == 0.0) {
696                    for (int i = 0; i < mInfoViewList.size(); i++) {
697                        mInfoViewList.get(i).setVisibility(View.GONE);
698                    }
699                }
700            }
701
702            @Override
703            public void onAnimationRepeat(Animation animation) {
704            }
705
706        });
707        startAnimation(mAnim);
708    }
709
710    @Override
711    public LayoutParams generateLayoutParams(AttributeSet attrs) {
712        return new BaseCardView.LayoutParams(getContext(), attrs);
713    }
714
715    @Override
716    protected LayoutParams generateDefaultLayoutParams() {
717        return new BaseCardView.LayoutParams(
718                LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
719    }
720
721    @Override
722    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
723        if (lp instanceof LayoutParams) {
724            return new LayoutParams((LayoutParams) lp);
725        } else {
726            return new LayoutParams(lp);
727        }
728    }
729
730    @Override
731    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
732        return p instanceof BaseCardView.LayoutParams;
733    }
734
735    /**
736     * Per-child layout information associated with BaseCardView.
737     */
738    public static class LayoutParams extends ViewGroup.LayoutParams {
739        public static final int VIEW_TYPE_MAIN = 0;
740        public static final int VIEW_TYPE_INFO = 1;
741        public static final int VIEW_TYPE_EXTRA = 2;
742
743        /**
744         * Card component type for the view associated with these LayoutParams.
745         */
746        @ViewDebug.ExportedProperty(category = "layout", mapping = {
747                @ViewDebug.IntToString(from = VIEW_TYPE_MAIN, to = "MAIN"),
748                @ViewDebug.IntToString(from = VIEW_TYPE_INFO, to = "INFO"),
749                @ViewDebug.IntToString(from = VIEW_TYPE_EXTRA, to = "EXTRA")
750        })
751        public int viewType = VIEW_TYPE_MAIN;
752
753        /**
754         * {@inheritDoc}
755         */
756        public LayoutParams(Context c, AttributeSet attrs) {
757            super(c, attrs);
758            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.lbBaseCardView_Layout);
759
760            viewType = a.getInt(
761                    R.styleable.lbBaseCardView_Layout_layout_viewType, VIEW_TYPE_MAIN);
762
763            a.recycle();
764        }
765
766        /**
767         * {@inheritDoc}
768         */
769        public LayoutParams(int width, int height) {
770            super(width, height);
771        }
772
773        /**
774         * {@inheritDoc}
775         */
776        public LayoutParams(ViewGroup.LayoutParams p) {
777            super(p);
778        }
779
780        /**
781         * Copy constructor. Clones the width, height, and View Type of the
782         * source.
783         *
784         * @param source The layout params to copy from.
785         */
786        public LayoutParams(LayoutParams source) {
787            super(source);
788
789            this.viewType = source.viewType;
790        }
791    }
792
793    // Helper animation class used in the animation of the info and extra
794    // fields vertically within the card
795    private class InfoOffsetAnimation extends Animation {
796        private float mStartValue;
797        private float mDelta;
798
799        public InfoOffsetAnimation(float start, float end) {
800            mStartValue = start;
801            mDelta = end - start;
802        }
803
804        @Override
805        protected void applyTransformation(float interpolatedTime, Transformation t) {
806            mInfoOffset = mStartValue + (interpolatedTime * mDelta);
807            requestLayout();
808        }
809    }
810
811    // Helper animation class used in the animation of the visible height
812    // for the info fields.
813    private class InfoHeightAnimation extends Animation {
814        private float mStartValue;
815        private float mDelta;
816
817        public InfoHeightAnimation(float start, float end) {
818            mStartValue = start;
819            mDelta = end - start;
820        }
821
822        @Override
823        protected void applyTransformation(float interpolatedTime, Transformation t) {
824            mInfoVisFraction = mStartValue + (interpolatedTime * mDelta);
825            requestLayout();
826        }
827    }
828
829    // Helper animation class used to animate the alpha for the info views
830    // when they are fading in or out of view.
831    private class InfoAlphaAnimation extends Animation {
832        private float mStartValue;
833        private float mDelta;
834
835        public InfoAlphaAnimation(float start, float end) {
836            mStartValue = start;
837            mDelta = end - start;
838        }
839
840        @Override
841        protected void applyTransformation(float interpolatedTime, Transformation t) {
842            mInfoAlpha = mStartValue + (interpolatedTime * mDelta);
843            for (int i = 0; i < mInfoViewList.size(); i++) {
844                mInfoViewList.get(i).setAlpha(mInfoAlpha);
845            }
846        }
847    }
848
849    @Override
850    public String toString() {
851        if (DEBUG) {
852            StringBuilder sb = new StringBuilder();
853            sb.append(this.getClass().getSimpleName()).append(" : ");
854            sb.append("cardType=");
855            switch(mCardType) {
856                case CARD_TYPE_MAIN_ONLY:
857                    sb.append("MAIN_ONLY");
858                    break;
859                case CARD_TYPE_INFO_OVER:
860                    sb.append("INFO_OVER");
861                    break;
862                case CARD_TYPE_INFO_UNDER:
863                    sb.append("INFO_UNDER");
864                    break;
865                case CARD_TYPE_INFO_UNDER_WITH_EXTRA:
866                    sb.append("INFO_UNDER_WITH_EXTRA");
867                    break;
868                default:
869                    sb.append("INVALID");
870                    break;
871            }
872            sb.append(" : ");
873            sb.append(mMainViewList.size()).append(" main views, ");
874            sb.append(mInfoViewList.size()).append(" info views, ");
875            sb.append(mExtraViewList.size()).append(" extra views : ");
876            sb.append("infoVisibility=").append(mInfoVisibility).append(" ");
877            sb.append("extraVisibility=").append(mExtraVisibility).append(" ");
878            sb.append("isActivated=").append(isActivated());
879            sb.append(" : ");
880            sb.append("isSelected=").append(isSelected());
881            return sb.toString();
882        } else {
883            return super.toString();
884        }
885    }
886}
887