com.tiancaicc.springfloatingactionmenu.SpringFloatingActionMenu.java Source code

Java tutorial

Introduction

Here is the source code for com.tiancaicc.springfloatingactionmenu.SpringFloatingActionMenu.java

Source

package com.tiancaicc.springfloatingactionmenu;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.net.Uri;
import android.os.Handler;
import android.support.annotation.ColorRes;
import android.support.annotation.IntDef;
import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;

import com.facebook.rebound.Spring;
import com.facebook.rebound.SpringSystem;
import com.facebook.rebound.ui.Util;
import com.melnykov.fab.FloatingActionButton;
import com.tumblr.backboard.Actor;
import com.tumblr.backboard.MotionProperty;
import com.tumblr.backboard.imitator.Imitator;
import com.tumblr.backboard.imitator.SpringImitator;
import com.tumblr.backboard.performer.MapPerformer;
import com.tumblr.backboard.performer.Performer;

import java.io.File;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;

/**
 * Copyright (C) 2016 tiancaiCC
 * <p>
 * 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
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * 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.
 */
public class SpringFloatingActionMenu extends FrameLayout implements ViewTreeObserver.OnGlobalLayoutListener {

    private static final String TAG = "SFAM";

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({ ANIMATION_TYPE_BLOOM, ANIMATION_TYPE_TUMBLR })
    public @interface ANIMATION_TYPE {
    }

    public static final int ANIMATION_TYPE_BLOOM = 0;
    public static final int ANIMATION_TYPE_TUMBLR = 1;

    private Context mContext;

    private FloatingActionButton mFAB;

    private View mRevealCircle;

    private View view;

    private ArrayList<MenuItem> mMenuItems;

    private ArrayList<ImageButton> mFollowCircles;

    private ArrayList<MenuItemView> mMenuItemViews;

    private ViewGroup mContainerView;

    private ArrayList<OnMenuActionListener> mActionListeners;

    private OnFabClickListener mOnFabClickListener;

    @ColorRes
    private int mRevealColor;

    private int mMenuItemCount;

    private int mGravity;

    private int mMarginSize = 16;

    private boolean mMenuOpen = false;

    private int mAnimationType;

    private int mRevealDuration = 600;

    private int mTumblrTimeInterval = 70;

    // fix animation not stable caused by FAB click too fast
    private boolean mAnimating = false;

    private boolean mEnableFollowAnimation = true;
    private boolean disableOpenMenuCapability = false;

    public SpringFloatingActionMenu(Builder builder) {
        super(builder.context);
        this.mContext = builder.context;
        this.mMenuItems = builder.menuItems;
        this.mFAB = builder.fab;
        this.mMenuItemCount = builder.menuItems.size();
        this.mGravity = builder.gravity;
        this.mActionListeners = builder.actionListeners;
        this.mRevealColor = builder.revealColor;
        this.mAnimationType = builder.animationType;
        this.mOnFabClickListener = builder.onFabClickListener;
        this.mEnableFollowAnimation = builder.enableFollowAnimation;
        init(mContext);
    }

    public void changeMenuItem(int index, String pn, String filePath) {
        try {
            MenuItem mi = mMenuItems.get(index);
            mi.setIconFilePath(filePath);
            mi.packageName = pn;
            mFollowCircles.get(index).setImageURI(Uri.fromFile(new File(filePath)));
            mFollowCircles.get(index).invalidate();
            mMenuItemViews.get(index).updateIcon();
        } catch (Exception e) {
            //do nothing
        }
    }

    public void setOpenAllAppsListener(OpenAllAppsListener listener) {
        mMenuItemViews.get(4).setListener(listener);
    }

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

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

    public SpringFloatingActionMenu(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    private void init(Context context) {

        this.mContext = context;
        // this mContainerView will be added when animation happened,
        // see DestroySelfSpringListener.onSpringActivate()
        mContainerView = new FrameLayout(context);

        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        view = inflater.inflate(R.layout.c_text_view, mContainerView, false);
        mContainerView.addView(view);

        mContainerView.addView(mRevealCircle = generateRevealCircle());

        //generate and add follow circles
        if (mEnableFollowAnimation) {
            mFollowCircles = generateFollowCircles();
            for (int i = mFollowCircles.size() - 1; i >= 0; i--) {
                // note follow circles is not added in container view, is just added in this SpringFloatingActionMenu
                addView(mFollowCircles.get(i));
            }
        }

        //generate and add menuItemViews
        mMenuItemViews = generateMenuItemViews();
        for (MenuItemView menuItemView : mMenuItemViews) {
            mContainerView.addView(menuItemView);
            addOnMenuActionListener(menuItemView);
        }

        mMenuItemViews.get(0).bringToFront();
        //        addView(mContainerView);
        //add FAB
        LayoutParams fablp = Utils.createWrapParams();
        fablp.gravity = mGravity;
        addView(mFAB, fablp);

        setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT));

        //add self to root view
        ViewGroup rootView = (ViewGroup) ((Activity) mContext).findViewById(android.R.id.content);
        rootView.addView(this);
        bringToFront();
        getViewTreeObserver().addOnGlobalLayoutListener(this);

        mFAB.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mAnimating) {
                    return;
                }
                if (disableOpenMenuCapability) {
                    return;
                }
                if (mOnFabClickListener != null) {
                    mOnFabClickListener.onClcik();
                }
                if (mMenuOpen) {
                    hideMenu();
                } else {
                    showMenu();
                }
            }
        });
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        Resources resources = getResources();
        Activity context = (Activity) mContext;

        // layout FAB and follow circles
        int fabWidth = mFAB.getMeasuredWidth();
        int fabHeight = mFAB.getMeasuredHeight();
        int bottomInsets = Utils.getInsetsBottom(context, this);

        int fabX = 0;
        int fabY = 0;
        if (mGravity == (Gravity.BOTTOM | Gravity.RIGHT)) {
            int marginX = Utils.dpToPx(mMarginSize, resources);
            int marginY = Utils.dpToPx(mMarginSize, resources) + bottomInsets;
            fabX = right - fabWidth - marginX;
            fabY = bottom - fabHeight - marginY;
            mFAB.layout(fabX, fabY, fabX + fabWidth, fabY + fabHeight);
        } else if (mGravity == (Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM)) {
            int marginY = Utils.dpToPx(mMarginSize, resources) + bottomInsets;
            fabX = right / 2 - fabWidth / 2;
            fabY = bottom - fabHeight - marginY;
            mFAB.layout(fabX, fabY, fabX + fabWidth, fabY + fabHeight);
        } else {
            throw new IllegalStateException("gravity only support bottom center and bottom right");
        }

        int fabCenterX = fabX + fabWidth / 2;
        int fabCenterY = fabY + fabHeight / 2;

        for (ImageButton circle : mFollowCircles) {
            int x = fabCenterX - circle.getWidth() / 2;
            int y = fabCenterY - circle.getHeight() / 2;
            circle.layout(x, y, x + circle.getMeasuredWidth(), y + circle.getMeasuredHeight());
        }

        int x = fabCenterX - mRevealCircle.getWidth() / 2;
        int y = fabCenterY - mRevealCircle.getHeight() / 2;
        mRevealCircle.layout(x, y, x + mRevealCircle.getMeasuredWidth(), y + mRevealCircle.getMeasuredHeight());

        //layout menu items
        layoutMenuItems(left, top, right, bottom);
    }

    private void layoutMenuItems(int left, int top, int right, int bottom) {
        switch (mAnimationType) {
        case ANIMATION_TYPE_BLOOM:
            layoutMenuItemsAsGrid(left, top, right, bottom);
            break;
        case ANIMATION_TYPE_TUMBLR:
            layoutMenuItemsAsTumblr(left, top, right, bottom);
            break;

        }
    }

    private void layoutMenuItemsAsGrid(int left, int top, int right, int bottom) {
        Resources resources = getResources();

        int edgeGap = Util.dpToPx(24, resources);

        int colCount = 3;
        int rowCount = mMenuItemCount % colCount == 0 ? mMenuItemCount / colCount : mMenuItemCount / colCount + 1;
        Log.d(TAG, "row count:" + rowCount);

        int itemHeight = mMenuItemViews.get(0).getMeasuredHeight();
        int itemWidth = mMenuItemViews.get(0).getMeasuredWidth();

        int containerWidth = getMeasuredWidth();
        int containerHeight = getMeasuredHeight();

        int itemGap = (containerWidth - 2 * edgeGap - colCount * itemWidth) / (colCount - 1);
        //top and bottom gap
        int tbGap = (containerHeight - rowCount * itemHeight - itemGap * (rowCount - 1)) / 2;

        for (int rowIdx = 0; rowIdx < rowCount; rowIdx++) {
            for (int colIdx = 0; colIdx < colCount; colIdx++) {
                int idxInItem = rowIdx * colCount + colIdx;
                if (idxInItem < mMenuItemCount) {
                    View item = mMenuItemViews.get(rowIdx * colCount + colIdx);
                    Log.d(TAG, "menu item index:" + (rowIdx * colCount + colIdx));
                    int itemLeft = edgeGap + colIdx * itemWidth + colIdx * itemGap;
                    int itemTop = tbGap + rowIdx * itemGap + rowIdx * itemHeight;
                    item.layout(itemLeft, itemTop, itemLeft + itemWidth, itemTop + itemHeight);
                } else {
                    break;
                }
            }
        }
    }

    private void layoutMenuItemsAsTumblr(int left, int top, int right, int bottom) {
        if (mMenuItemCount < 4 || mMenuItemCount > 7)
            throw new IllegalStateException("layout as tumblr style must have morn than 4 items,less than 7 items");
        int itemHeight = mMenuItemViews.get(0).getMeasuredHeight();
        int itemWidth = mMenuItemViews.get(0).getMeasuredWidth();

        int itemDiameter = mMenuItemViews.get(0).getDiameter();
        int itemRadius = itemDiameter / 2;

        int ringRadius = (int) (itemDiameter * 1.6);

        int containerWidth = getMeasuredWidth();
        int containerHeight = getMeasuredHeight();

        int ringCenterX = containerWidth / 2;
        int ringCenterY = containerHeight / 2;

        //layout first item at container center
        int firstX = containerWidth / 2 - itemRadius;
        int firstY = containerHeight / 2 - itemRadius;
        View firstItem = mMenuItemViews.get(0);
        firstItem.layout(firstX, firstY, firstX + itemWidth, firstY + itemHeight);

        double arcunit = 2 * Math.PI / (mMenuItemCount - 1);

        for (int i = 0; i < mMenuItemCount - 1; i++) {
            View item = mMenuItemViews.get(i + 1);
            double arc = arcunit * i;
            int x = (int) (ringCenterX + ringRadius * Math.sin(arc) - itemRadius);
            int y = (int) (ringCenterY - ringRadius * Math.cos(arc) - itemRadius);
            item.layout(x, y, x + item.getMeasuredWidth(), y + item.getMeasuredHeight());
        }

    }

    private ArrayList<ImageButton> generateFollowCircles() {

        int diameter = mFAB.getType() == FloatingActionButton.TYPE_NORMAL
                ? Utils.getDimension(mContext, R.dimen.fab_size_normal) - 2
                : Utils.getDimension(mContext, R.dimen.fab_size_mini);

        ArrayList<ImageButton> circles = new ArrayList<>(mMenuItems.size());
        for (MenuItem item : mMenuItems) {
            ImageButton circle = new ImageButton(mContext);
            circle.setScaleType(ImageView.ScaleType.FIT_CENTER);
            OvalShape ovalShape = new OvalShape();
            ShapeDrawable shapeDrawable = new ShapeDrawable(ovalShape);
            shapeDrawable.getPaint().setColor(getResources().getColor(item.getBgColor()));
            circle.setBackgroundDrawable(shapeDrawable);

            if (TextUtils.isEmpty(item.getIconFilePath())) {
                circle.setImageResource(item.getIcon());
            } else {
                circle.setImageURI(Uri.fromFile(new File(item.getIconFilePath())));
            }

            LayoutParams lp = new LayoutParams(diameter, diameter);
            circle.setLayoutParams(lp);
            circles.add(circle);
        }

        return circles;
    }

    private ArrayList<MenuItemView> generateMenuItemViews() {
        ArrayList<MenuItemView> menuItemViews = new ArrayList<>(mMenuItems.size());
        int i = 0;
        for (MenuItem item : mMenuItems) {
            MenuItemView menuItemView = new MenuItemView(mContext, item, (i != 0));
            menuItemView.setLayoutParams(Utils.createWrapParams());
            //            menuItemView.setOnClickListener(item.getOnClickListener());
            menuItemViews.add(menuItemView);
            i++;
        }
        return menuItemViews;
    }

    private View generateRevealCircle() {
        int diameter = mFAB.getType() == FloatingActionButton.TYPE_NORMAL
                ? Utils.getDimension(mContext, R.dimen.fab_size_normal)
                : Utils.getDimension(mContext, R.dimen.fab_size_mini);
        View view = new View(mContext);
        OvalShape ovalShape = new OvalShape();
        ShapeDrawable shapeDrawable = new ShapeDrawable(ovalShape);
        shapeDrawable.getPaint().setColor(ContextCompat.getColor(mContext, mRevealColor));
        view.setBackgroundDrawable(shapeDrawable);
        LayoutParams lp = new LayoutParams(diameter, diameter);
        view.setLayoutParams(lp);

        // Make view clickable to avoid clicks on any view located behind the menu
        view.setClickable(true);

        // note it is invisible, but will be visible while  animating
        view.setVisibility(View.GONE);
        return view;
    }

    @Override
    public void onGlobalLayout() {
        Log.d(TAG, "onGlobalLayout");
        if (mEnableFollowAnimation) {
            applyFollowAnimation();
        }
    }

    private void addOnMenuActionListener(OnMenuActionListener listener) {
        this.mActionListeners.add(listener);
    }

    public void disableOpenMenuCapability() {
        disableOpenMenuCapability = true;
    }

    public boolean isDisableOpenMenuCapability() {
        return disableOpenMenuCapability;
    }

    public void enableOpenMenuCapability() {
        disableOpenMenuCapability = false;
    }

    public void showMenu() {
        Log.d(TAG, "showMenu");
        switch (mAnimationType) {
        case ANIMATION_TYPE_BLOOM:
            applyBloomOpenAnimation();
            break;
        case ANIMATION_TYPE_TUMBLR:
            applyTumblrOpenAniamtion();
            break;

        }
        hideFollowCircles();
        revealIn();
        for (OnMenuActionListener listener : mActionListeners) {
            listener.onMenuOpen();
        }
        mMenuOpen = !mMenuOpen;
    }

    public void hideMenu() {
        Log.d(TAG, "hideMenu");
        switch (mAnimationType) {
        case ANIMATION_TYPE_BLOOM:
            applyBloomCloseAnimation();
            break;
        case ANIMATION_TYPE_TUMBLR:
            applyTumblrCloseAnimation();
            break;

        }
        showFollowCircles();
        revealOut();
        for (OnMenuActionListener listener : mActionListeners) {
            listener.onMenuClose();
        }
        mMenuOpen = !mMenuOpen;
    }

    public boolean isMenuOpen() {
        return mMenuOpen;
    }

    public void hideFollowCircles() {
        for (View view : mFollowCircles) {
            view.setVisibility(View.GONE);
        }
    }

    public void showFollowCircles() {
        for (View view : mFollowCircles) {
            view.setVisibility(View.VISIBLE);
        }
    }

    private void revealIn() {
        mRevealCircle.setVisibility(View.VISIBLE);
        mRevealCircle.animate().scaleX(100).scaleY(100).setDuration(mRevealDuration)
                .setInterpolator(new AccelerateDecelerateInterpolator()).setListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        super.onAnimationEnd(animation);
                        mAnimating = false;
                        animation.removeAllListeners();
                    }

                    @Override
                    public void onAnimationStart(Animator animation) {
                        super.onAnimationStart(animation);
                        mAnimating = true;
                    }
                }).start();
    }

    private void revealOut() {
        mRevealCircle.animate().scaleX(1).scaleY(1).setDuration(mRevealDuration)
                .setInterpolator(new AccelerateDecelerateInterpolator()).setListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        super.onAnimationEnd(animation);
                        mRevealCircle.setVisibility(View.GONE);
                        animation.removeAllListeners();
                        mAnimating = false;
                    }

                    @Override
                    public void onAnimationStart(Animator animation) {
                        super.onAnimationStart(animation);
                        mAnimating = true;
                    }
                }).start();
    }

    private void fadeIn() {
        int colorFrom = Color.parseColor("#00000000");
        int colorTo = Color.parseColor("#40000000");
        ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo);
        colorAnimation.setDuration(250); // milliseconds
        colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animator) {
                setBackgroundColor((int) animator.getAnimatedValue());
            }

        });
        colorAnimation.start();
    }

    public void fadeOut() {
        int colorFrom = Color.parseColor("#40000000");
        int colorTo = Color.parseColor("#00000000");
        ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo);
        colorAnimation.setDuration(250); // milliseconds
        colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animator) {
                setBackgroundColor((int) animator.getAnimatedValue());
            }

        });
        colorAnimation.start();
    }

    private void applyFollowAnimation() {
        /* Animation code */

        final SpringSystem springSystem = SpringSystem.create();

        // create the springs that control movement
        final Spring springX = springSystem.createSpring();
        final Spring springY = springSystem.createSpring();

        // bind circle movement to events
        new Actor.Builder(springSystem, mFAB)
                .addMotion(springX, Imitator.TRACK_DELTA, Imitator.FOLLOW_EXACT, MotionProperty.X)
                .addMotion(springY, Imitator.TRACK_DELTA, Imitator.FOLLOW_EXACT, MotionProperty.Y).build();

        // add springs to connect between the views
        final Spring[] followsX = new Spring[mMenuItemCount];
        final Spring[] followsY = new Spring[mMenuItemCount];

        for (int i = 0; i < mFollowCircles.size(); i++) {

            // create spring to bind views
            followsX[i] = springSystem.createSpring();
            followsY[i] = springSystem.createSpring();
            followsX[i].addListener(new Performer(mFollowCircles.get(i), View.TRANSLATION_X));
            followsY[i].addListener(new Performer(mFollowCircles.get(i), View.TRANSLATION_Y));

            // imitates another character
            final SpringImitator followX = new SpringImitator(followsX[i]);
            final SpringImitator followY = new SpringImitator(followsY[i]);

            //  imitate the previous character
            if (i == 0) {
                springX.addListener(followX);
                springY.addListener(followY);
            } else {
                followsX[i - 1].addListener(followX);
                followsY[i - 1].addListener(followY);
            }
        }
    }

    private void applyBloomOpenAnimation() {
        final SpringSystem springSystem = SpringSystem.create();

        for (int i = 0; i < mMenuItemCount; i++) {
            // create the springs that control movement
            final Spring springX = springSystem.createSpring();
            final Spring springY = springSystem.createSpring();

            MenuItemView menuItemView = mMenuItemViews.get(i);
            springX.addListener(new MapPerformer(menuItemView, View.X, mFAB.getLeft(), menuItemView.getLeft()));
            springY.addListener(new MapPerformer(menuItemView, View.Y, mFAB.getTop(), menuItemView.getTop()));
            DestroySelfSpringListener destroySelfSpringListener = new DestroySelfSpringListener(this,
                    mContainerView, true);
            springX.addListener(destroySelfSpringListener);
            springY.addListener(destroySelfSpringListener);
            springX.setEndValue(1);
            springY.setEndValue(1);
        }
    }

    private void applyBloomCloseAnimation() {
        final SpringSystem springSystem = SpringSystem.create();

        for (int i = 0; i < mMenuItemCount; i++) {
            // create the springs that control movement
            final Spring springX = springSystem.createSpring();
            final Spring springY = springSystem.createSpring();

            MenuItemView menuItemView = mMenuItemViews.get(i);
            springX.addListener(new MapPerformer(menuItemView, View.X, menuItemView.getLeft(), mFAB.getLeft()));
            springY.addListener(new MapPerformer(menuItemView, View.Y, menuItemView.getTop(), mFAB.getTop()));
            DestroySelfSpringListener destroySelfSpringListener = new DestroySelfSpringListener(this,
                    mContainerView, false);
            springX.addListener(destroySelfSpringListener);
            springY.addListener(destroySelfSpringListener);
            springX.setEndValue(1);
            springY.setEndValue(1);
        }
    }

    private void applyTumblrOpenAniamtion() {
        final View firstItem = mMenuItemViews.get(0);

        //make start position at center
        for (MenuItemView itemView : mMenuItemViews) {
            itemView.disableAlphaAnimation();
            itemView.setX(firstItem.getLeft());
            itemView.setY(firstItem.getY());
            itemView.setScaleX(0);
            itemView.setScaleY(0);
        }
        final SpringSystem springSystem = SpringSystem.create();

        final Spring springScaleX = springSystem.createSpring();
        final Spring springScaleY = springSystem.createSpring();

        springScaleX.addListener(new MapPerformer(firstItem, View.SCALE_X, 0, 1));
        springScaleY.addListener(new MapPerformer(firstItem, View.SCALE_Y, 0, 1));
        final DestroySelfSpringListener destroySelfSpringListener = new DestroySelfSpringListener(this,
                mContainerView, true);
        springScaleX.addListener(destroySelfSpringListener);
        springScaleY.addListener(destroySelfSpringListener);
        springScaleX.setEndValue(1);
        springScaleY.setEndValue(1);

        for (int i = 1; i < mMenuItemCount; i++) {
            final View menuItemView = mMenuItemViews.get(i);
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {

                    final Spring springScaleX = springSystem.createSpring();
                    final Spring springScaleY = springSystem.createSpring();

                    springScaleX.addListener(new MapPerformer(menuItemView, View.SCALE_X, 0, 1));
                    springScaleY.addListener(new MapPerformer(menuItemView, View.SCALE_Y, 0, 1));
                    final DestroySelfSpringListener destroySelfSpringListener = new DestroySelfSpringListener(
                            SpringFloatingActionMenu.this, mContainerView, true);
                    springScaleX.addListener(destroySelfSpringListener);
                    springScaleY.addListener(destroySelfSpringListener);
                    springScaleX.setEndValue(1);
                    springScaleY.setEndValue(1);

                    final Spring springX = springSystem.createSpring();
                    final Spring springY = springSystem.createSpring();

                    springX.addListener(
                            new MapPerformer(menuItemView, View.X, firstItem.getLeft(), menuItemView.getLeft()));
                    springY.addListener(
                            new MapPerformer(menuItemView, View.Y, firstItem.getTop(), menuItemView.getTop()));
                    springX.addListener(destroySelfSpringListener);
                    springY.addListener(destroySelfSpringListener);
                    springX.setEndValue(1);
                    springY.setEndValue(1);
                }
            }, mTumblrTimeInterval * (i - 1));
        }
    }

    private void applyTumblrCloseAnimation() {

        final int alphaDuration = 130;

        final SpringSystem springSystem = SpringSystem.create();

        final View firstItem = mMenuItemViews.get(0);
        final Spring springScaleX = springSystem.createSpring();
        final Spring springScaleY = springSystem.createSpring();

        springScaleX.addListener(new MapPerformer(firstItem, View.SCALE_X, 1, 0));
        springScaleY.addListener(new MapPerformer(firstItem, View.SCALE_Y, 1, 0));
        final DestroySelfSpringListener destroySelfSpringListener = new DestroySelfSpringListener(this,
                mContainerView, false);
        springScaleX.addListener(destroySelfSpringListener);
        springScaleY.addListener(destroySelfSpringListener);
        springScaleX.setEndValue(1);
        springScaleY.setEndValue(1);

        firstItem.animate().alpha(160);

        for (int i = mMenuItemCount - 1; i >= 1; i--) {
            final View menuItemView = mMenuItemViews.get(i);
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {

                    final Spring springScaleX = springSystem.createSpring();
                    final Spring springScaleY = springSystem.createSpring();

                    springScaleX.addListener(new MapPerformer(menuItemView, View.SCALE_X, 1, 0));
                    springScaleY.addListener(new MapPerformer(menuItemView, View.SCALE_Y, 1, 0));
                    final DestroySelfSpringListener destroySelfSpringListener = new DestroySelfSpringListener(
                            SpringFloatingActionMenu.this, mContainerView, false);
                    springScaleX.addListener(destroySelfSpringListener);
                    springScaleY.addListener(destroySelfSpringListener);
                    springScaleX.setEndValue(1);
                    springScaleY.setEndValue(1);

                    final Spring springX = springSystem.createSpring();
                    final Spring springY = springSystem.createSpring();

                    springX.addListener(
                            new MapPerformer(menuItemView, View.X, menuItemView.getLeft(), firstItem.getLeft()));
                    springY.addListener(
                            new MapPerformer(menuItemView, View.Y, menuItemView.getTop(), firstItem.getTop()));
                    springX.addListener(destroySelfSpringListener);
                    springY.addListener(destroySelfSpringListener);
                    springX.setEndValue(1);
                    springY.setEndValue(1);

                    menuItemView.animate().alpha(0).setDuration(alphaDuration);
                }
            }, mTumblrTimeInterval * (mMenuItemCount - i - 1));
        }
    }

    public FloatingActionButton getFAB() {
        return mFAB;
    }

    public static class Builder {

        private Context context;

        private ArrayList<MenuItem> menuItems = new ArrayList<>();

        private FloatingActionButton fab;

        private int gravity = Gravity.BOTTOM | Gravity.RIGHT;

        private int animationType = ANIMATION_TYPE_BLOOM;

        private boolean enableFollowAnimation = true;

        @ColorRes
        private int revealColor = android.R.color.holo_purple;

        private OnFabClickListener onFabClickListener;

        private ArrayList<OnMenuActionListener> actionListeners = new ArrayList<>();

        public Builder(Context context) {
            this.context = context;
        }

        public SpringFloatingActionMenu build() {
            return new SpringFloatingActionMenu(this);
        }

        public Builder addMenuItem(@ColorRes int bgColor, int icon, String label, @ColorRes int textColor,
                OnAppClickListener onClickListener) {
            menuItems.add(new MenuItem(bgColor, icon, label, textColor, onClickListener));
            return this;
        }

        public Builder addMenuItem(@ColorRes int bgColor, int icon, String label, @ColorRes int textColor,
                int diameter, OnAppClickListener onClickListener) {
            menuItems.add(new MenuItem(bgColor, icon, label, textColor, diameter, onClickListener));
            return this;
        }

        public Builder fab(FloatingActionButton fab) {
            this.fab = fab;
            return this;
        }

        public Builder gravity(int gravity) {
            this.gravity = gravity;
            return this;
        }

        public Builder onMenuActionListner(OnMenuActionListener listener) {
            actionListeners.add(listener);
            return this;
        }

        public Builder animationType(@ANIMATION_TYPE int type) {
            this.animationType = type;
            return this;
        }

        public Builder revealColor(@ColorRes int color) {
            this.revealColor = color;
            return this;
        }

        public Builder enableFollowAnimation(boolean enable) {
            this.enableFollowAnimation = enable;
            return this;
        }

        public Builder onFabClickListener(OnFabClickListener listener) {
            this.onFabClickListener = listener;
            return this;
        }
    }

}