it.gmariotti.cardslib.library.view.CardViewNative.java Source code

Java tutorial

Introduction

Here is the source code for it.gmariotti.cardslib.library.view.CardViewNative.java

Source

/*
 * ******************************************************************************
 *   Copyright (c) 2013-2014 Gabriele Mariotti.
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *  *****************************************************************************
 */

package it.gmariotti.cardslib.library.view;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.LayoutRes;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.LinearLayout;

import java.util.HashMap;

import it.gmariotti.cardslib.library.R;
import it.gmariotti.cardslib.library.internal.Card;
import it.gmariotti.cardslib.library.internal.CardExpand;
import it.gmariotti.cardslib.library.internal.CardHeader;
import it.gmariotti.cardslib.library.internal.CardThumbnail;
import it.gmariotti.cardslib.library.internal.ViewToClickToExpand;
import it.gmariotti.cardslib.library.view.base.CardViewWrapper;
import it.gmariotti.cardslib.library.view.component.CardHeaderView;
import it.gmariotti.cardslib.library.view.component.CardThumbnailView;
import it.gmariotti.cardslib.library.view.helper.CardViewHelper;
import it.gmariotti.cardslib.library.view.helper.CardViewHelperUtil;
import it.gmariotti.cardslib.library.view.listener.SwipeDismissViewTouchListener;

/**
* Card view
* </p>
* Use an XML layout file to display it.
* </p>
* First, you need an XML layout that will display the Card.
* <pre><code>
*  <it.gmariotti.cardslib.library.view.CardViewNative
*     android:id="@+id/carddemo_example_card3"
*     android:layout_width="match_parent"
*     android:layout_height="wrap_content"
*     android:layout_marginLeft="12dp"
*     android:layout_marginRight="12dp"
*     android:layout_marginTop="12dp"/>
* </code></pre>
* Then create a model:
* <pre><code>
*
*     //Create a Card
*     Card card = new Card(getContext());
*
*     //Create a CardHeader
*     CardHeader header = new CardHeader(getContext());
*
*     //Add Header to card
*     card.addCardHeader(header);
*
* </code></pre>
* Last get a reference to the `CardViewNative` from your code, and set your `Card.
* <pre><code>
*     //Set card in the cardView
*     CardViewNative cardView = (CardViewNative) getActivity().findViewById(R.id.carddemo);
*
*     cardView.setCard(card);
* </code></pre>
* You can easily build your layout.
* </p>
* The quickest way to start with this would be to copy one of this files and create your layout.
* Then you can inflate your layout in the `CardViewNative` using the attr: `card:card_layout_resourceID="@layout/my_layout`
*  Example:
* <pre><code>
*      <it.gmariotti.cardslib.library.view.CardViewNative
*       android:id="@+id/carddemo_thumb_url"
*        android:layout_width="match_parent"
*        android:layout_height="wrap_content"
*        android:layout_marginLeft="12dp"
*        android:layout_marginRight="12dp"
*        card:card_layout_resourceID="@layout/card_thumbnail_layout"
*        android:layout_marginTop="12dp"/>
* </code></pre>
* </p>
* @author Gabriele Mariotti (gabri.mariotti@gmail.com)
*/
public class CardViewNative extends android.support.v7.widget.CardView implements CardViewWrapper {

    protected static String TAG = "CardViewNative";

    //--------------------------------------------------------------------------
    // BaseCardView attribute
    //--------------------------------------------------------------------------

    /**
     * Card Model
     */
    protected Card mCard;

    /**
     * Default layout to apply to card
     */
    protected @LayoutRes int card_layout_resourceID = R.layout.native_card_layout;

    /**
     * Global View for this Component
     */
    protected View mInternalOuterView;

    /**
     * Header Compound View
     */
    protected CardHeaderView mInternalHeaderLayout;

    /**
     * Thumbnail Compound View
     */
    protected CardThumbnailView mInternalThumbnailLayout;

    /**
     * Used to recycle ui elements.
     */
    protected boolean mIsRecycle = false;

    /**
     * Used to replace inner layout elements.
     */
    protected boolean mForceReplaceInnerLayout = false;

    protected CardViewHelper mHelperImpl;

    //--------------------------------------------------------------------------
    // CardView attribute
    //--------------------------------------------------------------------------

    /**
     * {@link it.gmariotti.cardslib.library.internal.CardHeader} model
     */
    protected CardHeader mCardHeader;

    /**
     * {@link it.gmariotti.cardslib.library.internal.CardThumbnail} model
     */
    protected CardThumbnail mCardThumbnail;

    /**
     * {@link it.gmariotti.cardslib.library.internal.CardExpand} model
     */
    protected CardExpand mCardExpand;

    //--------------------------------------------------------------------------
    // Layout
    //--------------------------------------------------------------------------

    /**
     *  Main Layout
     */
    protected View mInternalMainCardLayout;

    /**
     *  Content Layout
     */
    protected View mInternalContentLayout;

    /**
     * Inner View.
     */
    protected View mInternalInnerView;

    /**
     *  Hidden layout used by expand/collapse action
     */
    protected View mInternalExpandLayout;

    /**
     * Expand Inner view
     */
    protected View mInternalExpandInnerView;

    /** Animator to expand/collapse */
    protected Animator mExpandAnimator;

    /**
     * Listener invoked when Expand Animator starts
     * It is used internally
     */
    protected OnExpandListAnimatorListener mOnExpandListAnimatorListener;

    //--------------------------------------------------------------------------
    // Constructor
    //--------------------------------------------------------------------------

    public CardViewNative(Context context) {
        this(context, null, 0);
    }

    public CardViewNative(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CardViewNative(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(attrs, defStyleAttr);

        mHelperImpl = CardViewHelperUtil.getInstance(context);
    }

    //--------------------------------------------------------------------------
    // Init
    //--------------------------------------------------------------------------

    /**
     * Initialize
     *
     * @param attrs
     * @param defStyle
     */
    protected void init(AttributeSet attrs, int defStyle) {
        //Init attrs
        initAttrs(attrs, defStyle);

        //Init view
        if (!isInEditMode())
            initView();
    }

    /**
     * Init custom attrs.
     *
     * @param attrs
     * @param defStyle
     */
    protected void initAttrs(AttributeSet attrs, int defStyle) {

        card_layout_resourceID = R.layout.native_card_layout;

        TypedArray a = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.card_options, defStyle,
                defStyle);

        try {
            card_layout_resourceID = a.getResourceId(R.styleable.card_options_card_layout_resourceID,
                    this.card_layout_resourceID);
        } finally {
            a.recycle();
        }
    }

    /**
     * Init View
     */
    protected void initView() {

        //Inflate outer view
        LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        mInternalOuterView = inflater.inflate(card_layout_resourceID, this, true);

        //Radius
        setRadius(getResources().getDimension(R.dimen.card_background_default_radius));

    }

    //--------------------------------------------------------------------------
    // Card
    //--------------------------------------------------------------------------

    /**
     * Add a {@link Card}.
     * It is very important to set all values and all components before launch this method.
     *
     * @param card {@link Card} model
     */
    @Override
    public void setCard(Card card) {

        mCard = card;

        if (card != null) {
            mCardHeader = card.getCardHeader();
            mCardThumbnail = card.getCardThumbnail();
            mCardExpand = card.getCardExpand();
        }

        //Retrieve all IDs
        if (!isRecycle()) {
            retrieveLayoutIDs();
        }

        //Build UI
        buildUI();
    }

    /**
     * Refreshes the card content (it doesn't inflate layouts again)
     *
     * @param card
     */
    public void refreshCard(Card card) {
        mIsRecycle = true;
        setCard(card);
        mIsRecycle = false;
    }

    /**
     * Refreshes the card content and replaces the inner layout elements (it inflates layouts again!)
     *
     * @param card
     */
    public void replaceCard(Card card) {
        mForceReplaceInnerLayout = true;
        refreshCard(card);
        mForceReplaceInnerLayout = false;
    }

    //--------------------------------------------------------------------------
    // Setup methods
    //--------------------------------------------------------------------------

    protected void buildUI() {

        if (mCard == null) {
            Log.e(TAG, "No card model found. Please use setCard(card) to set all values.");
            return;
        }
        mCard.setCardView(this);

        //Shadow
        setupShadowView();

        //Setup Header view
        setupHeaderView();

        //Setup Main View
        setupMainView();

        //setup Thumbnail
        setupThumbnailView();

        //Setup Expand View
        setupExpandView();

        //Setup Supplemental Actions
        setupSupplementalActions();

        //Setup Listeners
        setupListeners();

        //Setup Expand Action
        setupExpandAction();

        //Setup Drawable Resources
        setupDrawableResources();
    }

    /**
     * Retrieve all Layouts IDs
     */
    protected void retrieveLayoutIDs() {

        //Main Layout
        mInternalMainCardLayout = (View) findViewById(R.id.card_main_layout);

        //Get HeaderLayout
        mInternalHeaderLayout = (CardHeaderView) findViewById(R.id.card_header_layout);

        //Get ExpandHiddenView
        mInternalExpandLayout = (View) findViewById(R.id.card_content_expand_layout);

        //Get ContentLayout
        mInternalContentLayout = (View) findViewById(R.id.card_main_content_layout);

        //Get ThumbnailLayout
        mInternalThumbnailLayout = (CardThumbnailView) findViewById(R.id.card_thumbnail_layout);
    }

    /**
     * Sets up Shadow visibility
     *
     * @return
     */
    protected void setupShadowView() {
        if (mCard != null && mCard.getCardElevation() != null) {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
                ViewCompat.setElevation(this, mCard.getCardElevation());
            else
                ViewCompat.setElevation(this, mCard.getCardElevation());
        }
    }

    /**
     * Setup Header View
     */
    protected void setupHeaderView() {

        if (mCardHeader != null) {

            if (mInternalHeaderLayout != null) {
                mInternalHeaderLayout.setVisibility(VISIBLE);

                //Set recycle value (very important in a ListView)
                mInternalHeaderLayout.setRecycle(isRecycle());
                mInternalHeaderLayout.setForceReplaceInnerLayout(isForceReplaceInnerLayout());
                //Add Header View
                mInternalHeaderLayout.addCardHeader(mCardHeader);

            }
        } else {
            //No header. Hide layouts
            if (mInternalHeaderLayout != null) {
                mInternalHeaderLayout.setVisibility(GONE);
                //mInternalExpandLayout.setVisibility(View.GONE);

                if (isForceReplaceInnerLayout()) {
                    mInternalHeaderLayout.addCardHeader(null);
                    //mInternalHeaderLayout.removeAllViews();
                }
            }
        }
    }

    /**
     * Setup the Main View
     */
    protected void setupMainView() {
        if (mInternalContentLayout != null) {

            ViewGroup mParentGroup = null;
            try {
                mParentGroup = (ViewGroup) mInternalContentLayout;
            } catch (Exception e) {
                setRecycle(false);
            }

            //Check if view can be recycled
            //It can happen in a listView, and improves performances
            if (!isRecycle() || isForceReplaceInnerLayout()) {

                if (isForceReplaceInnerLayout() && mInternalContentLayout != null && mInternalInnerView != null)
                    ((ViewGroup) mInternalContentLayout).removeView(mInternalInnerView);

                mInternalInnerView = mCard.getInnerView(getContext(), (ViewGroup) mInternalContentLayout);
            } else {
                //View can be recycled.
                //Only setup Inner Elements
                if (mCard.getInnerLayout() > -1)
                    mCard.setupInnerViewElements(mParentGroup, mInternalInnerView);
            }
        }
    }

    /**
     * Setup the Thumbnail View
     */
    protected void setupThumbnailView() {
        if (mInternalThumbnailLayout != null) {
            if (mCardThumbnail != null) {
                mInternalThumbnailLayout.setVisibility(VISIBLE);
                mInternalThumbnailLayout.setRecycle(isRecycle());
                mInternalThumbnailLayout.setForceReplaceInnerLayout(isForceReplaceInnerLayout());
                mInternalThumbnailLayout.addCardThumbnail(mCardThumbnail);
            } else {
                mInternalThumbnailLayout.setVisibility(GONE);
            }
        }
    }

    /**
     * Setup Drawable Resources
     */
    protected void setupDrawableResources() {

        //Card
        if (mCard != null) {
            if (mCard.getBackgroundResourceId() != Card.DEFAULT_COLOR) {
                changeBackgroundResourceId(mCard.getBackgroundResourceId());
            } else if (mCard.getBackgroundResource() != null) {
                changeBackgroundResource(mCard.getBackgroundResource());
            }

            if (mCard.getBackgroundColorResourceId() != Card.DEFAULT_COLOR) {
                changeBackgroundColorResourceId(mCard.getBackgroundColorResourceId());
            }
        }
    }

    protected void setupSupplementalActions() {
        if (mCard != null)
            mCard.setupSupplementalActions();
    }

    //--------------------------------------------------------------------------
    // Listeners
    //--------------------------------------------------------------------------

    protected void setupExpandAction() {

        //Config ExpandLayout and its animation
        if (mInternalExpandLayout != null && ((mCardHeader != null && mCardHeader.isButtonExpandVisible())
                || mCard.getViewToClickToExpand() != null)) {

            //Create the expand/collapse animator
            mInternalExpandLayout.getViewTreeObserver()
                    .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {

                        @Override
                        public boolean onPreDraw() {
                            mInternalExpandLayout.getViewTreeObserver().removeOnPreDrawListener(this);

                            View parent = (View) mInternalExpandLayout.getParent();
                            final int widthSpec = View.MeasureSpec.makeMeasureSpec(
                                    parent.getMeasuredWidth() - parent.getPaddingLeft() - parent.getPaddingRight(),
                                    View.MeasureSpec.AT_MOST);
                            final int heightSpec = View.MeasureSpec.makeMeasureSpec(0,
                                    View.MeasureSpec.UNSPECIFIED);
                            mInternalExpandLayout.measure(widthSpec, heightSpec);

                            mExpandAnimator = ExpandCollapseHelper.createSlideAnimator(
                                    (CardViewNative) mCard.getCardView(), 0,
                                    mInternalExpandLayout.getMeasuredHeight());
                            return true;
                        }
                    });
        }

        //Setup action and callback
        setupExpandCollapseActionListener();
    }

    /**
     * Setup All listeners
     */
    @SuppressWarnings("deprecation")
    @SuppressLint("NewApi")
    protected void setupListeners() {

        //Swipe listener
        if (mCard.isSwipeable()) {
            this.setOnTouchListener(new SwipeDismissViewTouchListener(this, mCard,
                    new SwipeDismissViewTouchListener.DismissCallbacks() {
                        @Override
                        public boolean canDismiss(Card card) {
                            return card.isSwipeable();
                        }

                        @Override
                        public void onDismiss(CardViewWrapper cardView, Card card) {
                            final ViewGroup vg = (ViewGroup) (((View) cardView).getParent());
                            if (vg != null) {
                                vg.removeView((View) cardView);
                                card.onSwipeCard();
                            }
                        }
                    }));
        } else {
            this.setOnTouchListener(null);
        }

        //OnClick listeners and partial listener

        //Reset Partial Listeners
        resetPartialListeners();

        if (mCard.isClickable()) {
            //Set the onClickListener
            if (!mCard.isMultiChoiceEnabled()) {
                if (mCard.getOnClickListener() != null) {
                    this.setOnClickListener(new OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            if (mCard.getOnClickListener() != null)
                                mCard.getOnClickListener().onClick(mCard, v);
                        }
                    });

                    //Prevent multiple events
                    //if (!mCard.isSwipeable() && mCard.getOnSwipeListener() == null) {
                    //    this.setClickable(true);
                    //}

                } else {
                    HashMap<Integer, Card.OnCardClickListener> mMultipleOnClickListner = mCard
                            .getMultipleOnClickListener();
                    if (mMultipleOnClickListner != null && !mMultipleOnClickListner.isEmpty()) {

                        for (int key : mMultipleOnClickListner.keySet()) {
                            View viewClickable = decodeAreaOnClickListener(key);
                            final Card.OnCardClickListener mListener = mMultipleOnClickListner.get(key);
                            if (viewClickable != null) {
                                //Add listener to this view
                                viewClickable.setOnClickListener(new OnClickListener() {
                                    @Override
                                    public void onClick(View v) {
                                        //Callback to card listener
                                        if (mListener != null)
                                            mListener.onClick(mCard, v);
                                    }
                                });

                                //Add Selector to this view
                                if (key > Card.CLICK_LISTENER_ALL_VIEW) {
                                    mHelperImpl.setBackground(viewClickable, mHelperImpl.getResourceFromAttrs(
                                            getContext(), android.R.attr.selectableItemBackground));
                                }
                            }
                        }
                    } else {
                        //There aren't listners
                        this.setClickable(false);
                    }
                }
            }
        } else {
            this.setClickable(false);
        }

        //LongClick listener
        if (mCard.isLongClickable()) {
            this.setOnLongClickListener(new OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    if (mCard.getOnLongClickListener() != null)
                        return mCard.getOnLongClickListener().onLongClick(mCard, v);
                    return false;
                }
            });
        } else {
            this.setLongClickable(false);
        }
    }

    /**
     * Reset all partial listeners
     */
    protected void resetPartialListeners() {
        View viewClickable = decodeAreaOnClickListener(Card.CLICK_LISTENER_HEADER_VIEW);
        if (viewClickable != null)
            viewClickable.setClickable(false);

        viewClickable = decodeAreaOnClickListener(Card.CLICK_LISTENER_THUMBNAIL_VIEW);
        if (viewClickable != null)
            viewClickable.setClickable(false);

        viewClickable = decodeAreaOnClickListener(Card.CLICK_LISTENER_CONTENT_VIEW);
        if (viewClickable != null)
            viewClickable.setClickable(false);
    }

    /**
     *
     * @param area
     * @return
     */
    protected View decodeAreaOnClickListener(int area) {

        if (area < Card.CLICK_LISTENER_ALL_VIEW && area > Card.CLICK_LISTENER_CONTENT_VIEW)
            return null;

        View view = null;

        switch (area) {
        case Card.CLICK_LISTENER_ALL_VIEW:
            view = this;
            break;
        case Card.CLICK_LISTENER_HEADER_VIEW:
            view = mInternalHeaderLayout;
            break;
        case Card.CLICK_LISTENER_THUMBNAIL_VIEW:
            view = mInternalThumbnailLayout;
            break;
        case Card.CLICK_LISTENER_CONTENT_VIEW:
            view = mInternalContentLayout;
            break;
        case Card.CLICK_LISTENER_ACTIONAREA1_VIEW:
            view = mInternalMainCardLayout;
            break;
        default:
            break;
        }
        return view;
    }

    //--------------------------------------------------------------------------
    // Expandable Actions and Listeners
    //--------------------------------------------------------------------------

    /**
     * Add ClickListener to expand and collapse hidden view
     */
    protected void setupExpandCollapseActionListener() {
        if (mInternalExpandLayout != null) {
            mInternalExpandLayout.setVisibility(View.GONE);

            boolean internal_blockForLongClickOnImageButtonExpand = false;
            ViewToClickToExpand viewToClickToExpand = null;

            //ButtonExpandVisible has a priority to viewClickToExpand
            if (mCardHeader != null && mCardHeader.isButtonExpandVisible()) {

                viewToClickToExpand = ViewToClickToExpand.builder()
                        .setupView(mInternalHeaderLayout.getImageButtonExpand()).highlightView(true);
                internal_blockForLongClickOnImageButtonExpand = true;

            } else if (mCard.getViewToClickToExpand() != null) {

                viewToClickToExpand = mCard.getViewToClickToExpand();
            }

            if (viewToClickToExpand != null) {

                TitleViewOnClickListener titleViewOnClickListener = new TitleViewOnClickListener(
                        mInternalExpandLayout, mCard, viewToClickToExpand.isViewToSelect());

                /*if (mCardHeader!=null && mCardHeader.isButtonExpandVisible() && mInternalHeaderLayout != null) {
                mInternalHeaderLayout.setOnClickExpandCollapseActionListener(titleViewOnClickListener);
                }*/

                View viewToClick = viewToClickToExpand.getViewToClick();
                if (viewToClick != null) {

                    if (internal_blockForLongClickOnImageButtonExpand) {
                        //The long click on Header button is now allowed
                        viewToClick.setOnClickListener(titleViewOnClickListener);
                    } else {
                        if (viewToClickToExpand.isUseLongClick()) {
                            viewToClick.setOnLongClickListener(
                                    new TitleViewOnLongClickListener(titleViewOnClickListener));
                        } else {
                            viewToClick.setOnClickListener(titleViewOnClickListener);
                        }
                    }
                } else {
                    ViewToClickToExpand.CardElementUI cardElementUI = viewToClickToExpand.getCardElementUIToClick();
                    if (cardElementUI != null) {
                        switch (cardElementUI) {
                        case CARD:
                            viewToClick = this;
                            break;
                        case HEADER:
                            viewToClick = getInternalHeaderLayout();
                            break;
                        case THUMBNAIL:
                            viewToClick = getInternalThumbnailLayout();
                            break;
                        case MAIN_CONTENT:
                            viewToClick = getInternalContentLayout();
                            break;
                        }
                        if (viewToClick != null) {
                            if (viewToClickToExpand.isUseLongClick()) {
                                viewToClick.setOnLongClickListener(
                                        new TitleViewOnLongClickListener(titleViewOnClickListener));
                            } else {
                                viewToClick.setOnClickListener(titleViewOnClickListener);
                            }
                        }
                    }
                }

                if (isExpanded()) {
                    //Make layout visible and button selected
                    mInternalExpandLayout.setVisibility(View.VISIBLE);
                    if (viewToClick != null) {
                        if (viewToClickToExpand.isViewToSelect())
                            viewToClick.setSelected(true);
                    }
                } else {
                    //Make layout hidden and button not selected
                    mInternalExpandLayout.setVisibility(View.GONE);
                    if (viewToClick != null) {
                        if (viewToClickToExpand.isViewToSelect())
                            viewToClick.setSelected(false);
                    }
                }
            }

        }
    }

    /**
     * Listener to expand/collapse hidden Expand Layout
     * It starts animation
     */
    protected class TitleViewOnLongClickListener implements OnLongClickListener {

        TitleViewOnClickListener mOnClickListener;

        private TitleViewOnLongClickListener(TitleViewOnClickListener onClickListener) {
            mOnClickListener = onClickListener;
        }

        @Override
        public boolean onLongClick(View view) {
            if (mOnClickListener != null) {
                mOnClickListener.onClick(view);
                return true;
            }
            return false;
        }
    }

    private class ExpandContainerHelper {

        private View contentParent;
        private Card card;
        private boolean viewToSelect = true;

        private ExpandContainerHelper(View contentParent, Card card, boolean viewToSelect) {
            this.contentParent = contentParent;
            this.card = card;
            this.viewToSelect = viewToSelect;
        }

        public CardViewNative getCardView() {
            return (CardViewNative) card.getCardView();
        }
    }

    private static class ExpandCollapseHelper {

        /**
         * Expanding animator.
         */
        private static void animateExpanding(final ExpandContainerHelper helper) {

            if (helper.getCardView().getOnExpandListAnimatorListener() != null) {
                //List Animator
                helper.getCardView().getOnExpandListAnimatorListener().onExpandStart(helper.getCardView(),
                        helper.contentParent);
            } else {
                //Std animator
                helper.contentParent.setVisibility(View.VISIBLE);
                if (helper.getCardView().mExpandAnimator != null) {
                    helper.getCardView().mExpandAnimator.addListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animation) {
                            helper.card.setExpanded(true);
                            //Callback
                            if (helper.card.getOnExpandAnimatorEndListener() != null)
                                helper.card.getOnExpandAnimatorEndListener().onExpandEnd(helper.card);
                        }
                    });
                    helper.getCardView().mExpandAnimator.start();
                } else {
                    if (helper.card.getOnExpandAnimatorEndListener() != null)
                        helper.card.getOnExpandAnimatorEndListener().onExpandEnd(helper.card);
                    Log.w(TAG, "Does the card have the ViewToClickToExpand?");
                }
            }
        }

        /**
         * Collapse animator
         */
        private static void animateCollapsing(final ExpandContainerHelper helper) {

            if (helper.getCardView().getOnExpandListAnimatorListener() != null) {
                //There is a List Animator.
                helper.getCardView().getOnExpandListAnimatorListener().onCollapseStart(helper.getCardView(),
                        helper.contentParent);
            } else {
                //Std animator
                int origHeight = helper.contentParent.getHeight();

                ValueAnimator animator = createSlideAnimator(helper.getCardView(), origHeight, 0);
                animator.addListener(new Animator.AnimatorListener() {
                    @Override
                    public void onAnimationStart(Animator animator) {
                    }

                    @Override
                    public void onAnimationEnd(Animator animator) {
                        helper.contentParent.setVisibility(View.GONE);
                        helper.card.setExpanded(false);
                        //Callback
                        if (helper.card.getOnCollapseAnimatorEndListener() != null)
                            helper.card.getOnCollapseAnimatorEndListener().onCollapseEnd(helper.card);
                    }

                    @Override
                    public void onAnimationCancel(Animator animator) {
                    }

                    @Override
                    public void onAnimationRepeat(Animator animator) {
                    }
                });
                animator.start();
            }
        }

        /**
         * Create the Slide Animator invoked when the expand/collapse button is clicked
         */
        protected static ValueAnimator createSlideAnimator(final CardViewNative cardView, int start, int end) {
            ValueAnimator animator = ValueAnimator.ofInt(start, end);

            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator valueAnimator) {
                    int value = (Integer) valueAnimator.getAnimatedValue();

                    ViewGroup.LayoutParams layoutParams = cardView.mInternalExpandLayout.getLayoutParams();
                    layoutParams.height = value;
                    cardView.mInternalExpandLayout.setLayoutParams(layoutParams);
                }
            });
            return animator;
        }

    }

    /**
     * Setup Expand View
     */
    protected void setupExpandView() {
        if (mInternalExpandLayout != null && mCardExpand != null) {

            //Check if view can be recycled
            //It can happen in a listView, and improves performances
            if (!isRecycle() || isForceReplaceInnerLayout()) {

                if (isForceReplaceInnerLayout() && mInternalExpandLayout != null
                        && mInternalExpandInnerView != null)
                    ((ViewGroup) mInternalExpandLayout).removeView(mInternalExpandInnerView);

                mInternalExpandInnerView = mCardExpand.getInnerView(getContext(),
                        (ViewGroup) mInternalExpandLayout);
            } else {
                //View can be recycled.
                //Only setup Inner Elements
                if (mCardExpand.getInnerLayout() > -1)
                    mCardExpand.setupInnerViewElements((ViewGroup) mInternalExpandLayout, mInternalExpandInnerView);
            }

            ViewGroup.LayoutParams layoutParams = mInternalExpandLayout.getLayoutParams();
            layoutParams.height = LinearLayout.LayoutParams.WRAP_CONTENT;
            mInternalExpandLayout.setLayoutParams(layoutParams);
        }
    }

    public void doToggleExpand() {

        if (mInternalExpandLayout != null) {
            ExpandContainerHelper helper = new ExpandContainerHelper(mInternalExpandLayout, mCard, false);

            boolean isVisible = mInternalExpandLayout.getVisibility() == View.VISIBLE;
            if (isVisible) {
                ExpandCollapseHelper.animateCollapsing(helper);
            } else {
                ExpandCollapseHelper.animateExpanding(helper);
            }
        }
    }

    public void doExpand() {

        if (mInternalExpandLayout != null) {
            ExpandContainerHelper helper = new ExpandContainerHelper(mInternalExpandLayout, mCard, false);

            boolean isVisible = mInternalExpandLayout.getVisibility() == View.VISIBLE;
            if (!isVisible) {
                ExpandCollapseHelper.animateExpanding(helper);
            }
        }
    }

    public void doCollapse() {

        if (mInternalExpandLayout != null) {
            ExpandContainerHelper helper = new ExpandContainerHelper(mInternalExpandLayout, mCard, false);

            boolean isVisible = mInternalExpandLayout.getVisibility() == View.VISIBLE;
            if (isVisible) {
                ExpandCollapseHelper.animateCollapsing(helper);
            }
        }
    }

    /**
     * Listener to expand/collapse hidden Expand Layout
     * It starts animation
     */
    protected class TitleViewOnClickListener implements View.OnClickListener {

        ExpandContainerHelper mExpandContainerHelper;

        private TitleViewOnClickListener(View contentParent, Card card) {
            this(contentParent, card, true);
        }

        private TitleViewOnClickListener(View contentParent, Card card, boolean viewToSelect) {
            mExpandContainerHelper = new ExpandContainerHelper(contentParent, card, viewToSelect);
        }

        @Override
        public void onClick(View view) {
            boolean isVisible = mExpandContainerHelper.contentParent.getVisibility() == View.VISIBLE;
            if (isVisible) {
                ExpandCollapseHelper.animateCollapsing(mExpandContainerHelper);
                if (mExpandContainerHelper.viewToSelect)
                    view.setSelected(false);
            } else {
                ExpandCollapseHelper.animateExpanding(mExpandContainerHelper);
                if (mExpandContainerHelper.viewToSelect)
                    view.setSelected(true);
            }
        }
    }

    @Override
    protected void onSizeChanged(int xNew, int yNew, int xOld, int yOld) {
        super.onSizeChanged(xNew, yNew, xOld, yOld);
    }

    // -------------------------------------------------------------
    //  OnExpandListAnimator Interface and Listener
    // -------------------------------------------------------------

    /**
     * Returns the listener invoked when expand/collpase animation starts
     * It is used internally
     *
     * @return listener
     */
    public OnExpandListAnimatorListener getOnExpandListAnimatorListener() {
        return mOnExpandListAnimatorListener;
    }

    /**
     * Sets the listener invoked when expand/collapse animation starts
     * It is used internally. Don't override it.
     *
     * @param onExpandListAnimatorListener listener
     */
    @Override
    public void setOnExpandListAnimatorListener(
            CardViewWrapper.OnExpandListAnimatorListener onExpandListAnimatorListener) {
        this.mOnExpandListAnimatorListener = onExpandListAnimatorListener;
    }

    // -------------------------------------------------------------
    //  ChangeBackground
    // -------------------------------------------------------------

    /**
     * Changes dynamically the drawable resource to override the style of MainLayout.
     *
     * @param drawableResourceId drawable resource Id
     */
    @Override
    public void changeBackgroundResourceId(int drawableResourceId) {
        if (drawableResourceId != Card.DEFAULT_COLOR) {
            changeBackgroundResource(getResources().getDrawable(drawableResourceId));
        }
    }

    /**
     * Changes dynamically the drawable resource to override the style of MainLayout.
     *
     * @param drawableResource drawable resource
     */
    @Override
    public void changeBackgroundResource(Drawable drawableResource) {
        if (drawableResource != null) {
            if (mInternalMainCardLayout != null) {
                mHelperImpl.setBackground(mInternalMainCardLayout, drawableResource);
            }
        }
    }

    /**
     * Changes dynamically the color of the background card
     *
     * @param colorResourceId color resource Id
     */
    @Override
    public void changeBackgroundColorResourceId(int colorResourceId) {
        if (colorResourceId != Card.DEFAULT_COLOR) {
            //this.setBackgroundDrawable(mHelperImpl.getResourceFromAttrs(getContext(),R.attr.cardBackgroundColor));
            mInternalMainCardLayout.setBackgroundColor(getResources().getColor(colorResourceId));
        }
    }

    // -------------------------------------------------------------
    //  Bitmap export
    // -------------------------------------------------------------

    /**
     * Create a {@link android.graphics.Bitmap} from CardView
     * @return
     */
    public Bitmap createBitmap() {

        if (getWidth() <= 0 && getHeight() <= 0) {
            int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
            measure(spec, spec);
            layout(0, 0, getMeasuredWidth(), getMeasuredHeight());
        }

        Bitmap b = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
        Canvas c = new Canvas(b);
        draw(c);
        return b;
    }

    //--------------------------------------------------------------------------
    // Getters and Setters
    //--------------------------------------------------------------------------

    public View getInternalOuterView() {
        return mInternalOuterView;
    }

    /**
     * Returns {@link Card} model
     *
     * @return  {@link Card} model
     */
    public Card getCard() {
        return mCard;
    }

    /**
     * Returns the view used for Header
     *
     * @return {@link CardHeaderView}
     */
    public CardHeaderView getInternalHeaderLayout() {
        return mInternalHeaderLayout;
    }

    /**
     * Returns the view used by Thumbnail
     *
     * @return {@link CardThumbnailView}
     */
    @Override
    public CardThumbnailView getInternalThumbnailLayout() {
        return mInternalThumbnailLayout;
    }

    /**
     * Indicates if view can recycle ui elements.
     *
     * @return <code>true</code> if views can recycle ui elements
     */
    public boolean isRecycle() {
        return mIsRecycle;
    }

    /**
     * Sets if view can recycle ui elements
     *
     * @param isRecycle  <code>true</code> to recycle
     */
    @Override
    public void setRecycle(boolean isRecycle) {
        this.mIsRecycle = isRecycle;
    }

    /**
     * Indicates if inner layout have to be replaced
     *
     * @return <code>true</code> if inner layout can be recycled
     */
    public boolean isForceReplaceInnerLayout() {
        return mForceReplaceInnerLayout;
    }

    /**
     * Sets if inner layout have to be replaced
     *
     * @param forceReplaceInnerLayout  <code>true</code> to recycle
     */
    @Override
    public void setForceReplaceInnerLayout(boolean forceReplaceInnerLayout) {
        this.mForceReplaceInnerLayout = forceReplaceInnerLayout;
    }

    /**
     * Returns the view used by Expand Layout
     *
     * @return {@link View} used by Expand Layout
     */
    public View getInternalExpandLayout() {
        return mInternalExpandLayout;
    }

    /**
     * FIXME
     * @return
     */
    public View getInternalContentLayout() {
        return mInternalContentLayout;
    }

    /**
     * FIXME
     * @return
     */
    public View getInternalInnerView() {
        return mInternalInnerView;
    }

    /**
     * Indicates if the card is expanded or collapsed
     *
     * @return <code>true</code> if the card is expanded
     */
    public boolean isExpanded() {
        if (mCard != null) {
            return mCard.isExpanded();
        } else
            return false;
    }

    /**
     * Sets the card as expanded or collapsed
     *
     * @param expanded  <code>true</code> if the card is expanded
     */
    public void setExpanded(boolean expanded) {
        if (mCard != null) {
            mCard.setExpanded(expanded);
        }
    }

    @Override
    public boolean isNative() {
        return true;
    }

    /**
     * Retrieves the InternalMainCardGlobalLayout.
     *
     * @return
     */
    public View getInternalMainCardLayout() {
        return mInternalMainCardLayout;
    }
}