Android Open Source - material-menu Material Menu Drawable






From Project

Back to project page material-menu.

License

The source code is released under:

Apache License

If you think the Android project material-menu listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*
 * Copyright (C) 2014 Balys Valentukevicius
 */* w w  w.j  a v  a 2 s.  co m*/
 * 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 com.balysv.materialmenu;


import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.util.TypedValue;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;

import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.AnimatorListenerAdapter;
import com.nineoldandroids.animation.ObjectAnimator;
import com.nineoldandroids.util.Property;

import static android.graphics.Paint.Style;


public class MaterialMenuDrawable extends Drawable implements Animatable {

    public enum IconState {
        BURGER, ARROW, X, CHECK
    }

    public enum AnimationState {
        BURGER_ARROW, BURGER_X, ARROW_X, ARROW_CHECK, BURGER_CHECK, X_CHECK;

        public IconState getFirstState() {
            switch (this) {
                case BURGER_ARROW:
                    return IconState.BURGER;
                case BURGER_X:
                    return IconState.BURGER;
                case ARROW_X:
                    return IconState.ARROW;
                case ARROW_CHECK:
                    return IconState.ARROW;
                case BURGER_CHECK:
                    return IconState.BURGER;
                case X_CHECK:
                    return IconState.X;
                default:
                    return null;
            }
        }

        public IconState getSecondState() {
            switch (this) {
                case BURGER_ARROW:
                    return IconState.ARROW;
                case BURGER_X:
                    return IconState.X;
                case ARROW_X:
                    return IconState.X;
                case ARROW_CHECK:
                    return IconState.CHECK;
                case BURGER_CHECK:
                    return IconState.CHECK;
                case X_CHECK:
                    return IconState.CHECK;
                default:
                    return null;
            }
        }
    }

    public enum Stroke {
        /**
         * 3 dip
         */
        REGULAR(3),
        /**
         * 2 dip
         */
        THIN(2),
        /**
         * 1 dip
         */
        EXTRA_THIN(1);

        private final int strokeWidth;

        Stroke(int strokeWidth) {
            this.strokeWidth = strokeWidth;
        }

        protected static Stroke valueOf(int strokeWidth) {
            switch (strokeWidth) {
                case 3:
                    return REGULAR;
                case 2:
                    return THIN;
                case 1:
                    return EXTRA_THIN;
                default:
                    return THIN;
            }
        }
    }

    public static final int DEFAULT_COLOR              = Color.WHITE;
    public static final int DEFAULT_SCALE              = 1;
    public static final int DEFAULT_TRANSFORM_DURATION = 800;
    public static final int DEFAULT_PRESSED_DURATION   = 400;

    private static final int BASE_DRAWABLE_WIDTH  = 40;
    private static final int BASE_DRAWABLE_HEIGHT = 40;
    private static final int BASE_ICON_WIDTH      = 20;
    private static final int BASE_CIRCLE_RADIUS   = 18;

    private static final float ARROW_MID_LINE_ANGLE = 180;
    private static final float ARROW_TOP_LINE_ANGLE = 135;
    private static final float ARROW_BOT_LINE_ANGLE = 225;
    private static final float X_TOP_LINE_ANGLE     = 44;
    private static final float X_BOT_LINE_ANGLE     = -44;
    private static final float X_ROTATION_ANGLE     = 90;
    private static final float CHECK_MIDDLE_ANGLE   = 135;
    private static final float CHECK_BOTTOM_ANGLE   = -90;

    private static final float TRANSFORMATION_START = 0;
    private static final float TRANSFORMATION_MID   = 1.0f;
    private static final float TRANSFORMATION_END   = 2.0f;

    private static final int DEFAULT_CIRCLE_ALPHA = 200;

    private final float diph;
    private final float dip1;
    private final float dip2;
    private final float dip3;
    private final float dip4;
    private final float dip6;
    private final float dip8;

    private final int   width;
    private final int   height;
    private final float strokeWidth;
    private final float iconWidth;
    private final float topPadding;
    private final float sidePadding;
    private final float circleRadius;

    private final Stroke stroke;

    private final Object lock = new Object();

    private final Paint iconPaint   = new Paint();
    private final Paint circlePaint = new Paint();

    private float   transformationValue   = 0f;
    private float   pressedProgressValue  = 0f;
    private boolean transformationRunning = false;

    private IconState      currentIconState = IconState.BURGER;
    private AnimationState animationState   = AnimationState.BURGER_ARROW;

    private IconState animatingIconState;
    private boolean   drawTouchCircle;
    private boolean   neverDrawTouch;
    private boolean   rtlEnabled;

    private ObjectAnimator transformation;
    private ObjectAnimator pressedCircle;

    private MaterialMenuState materialMenuState;

    public MaterialMenuDrawable(Context context, int color, Stroke stroke) {
        this(context, color, stroke, DEFAULT_SCALE, DEFAULT_TRANSFORM_DURATION, DEFAULT_PRESSED_DURATION);
    }

    public MaterialMenuDrawable(Context context, int color, Stroke stroke, int transformDuration, int pressedDuration) {
        this(context, color, stroke, DEFAULT_SCALE, transformDuration, pressedDuration);
    }

    public MaterialMenuDrawable(Context context, int color, Stroke stroke, int scale, int transformDuration, int pressedDuration) {
        Resources resources = context.getResources();
        // convert each separately due to various densities
        this.dip1 = dpToPx(resources, 1) * scale;
        this.dip2 = dpToPx(resources, 2) * scale;
        this.dip3 = dpToPx(resources, 3) * scale;
        this.dip4 = dpToPx(resources, 4) * scale;
        this.dip6 = dpToPx(resources, 6) * scale;
        this.dip8 = dpToPx(resources, 8) * scale;
        this.diph = dip1 / 2;

        this.stroke = stroke;
        this.width = (int) (dpToPx(resources, BASE_DRAWABLE_WIDTH) * scale);
        this.height = (int) (dpToPx(resources, BASE_DRAWABLE_HEIGHT) * scale);
        this.iconWidth = dpToPx(resources, BASE_ICON_WIDTH) * scale;
        this.circleRadius = dpToPx(resources, BASE_CIRCLE_RADIUS) * scale;
        this.strokeWidth = dpToPx(resources, stroke.strokeWidth) * scale;

        this.sidePadding = (width - iconWidth) / 2;
        this.topPadding = (height - 5 * dip3) / 2;

        initPaint(color);
        initAnimations(transformDuration, pressedDuration);

        materialMenuState = new MaterialMenuState();
    }

    private MaterialMenuDrawable(int color, Stroke stroke, long transformDuration, long pressedDuration,
        int width, int height, float iconWidth, float circleRadius, float strokeWidth, float dip1
    ) {
        this.dip1 = dip1;
        this.dip2 = dip1 * 2;
        this.dip3 = dip1 * 3;
        this.dip4 = dip1 * 4;
        this.dip6 = dip1 * 6;
        this.dip8 = dip1 * 8;
        this.diph = dip1 / 2;
        this.stroke = stroke;
        this.width = width;
        this.height = height;
        this.iconWidth = iconWidth;
        this.circleRadius = circleRadius;
        this.strokeWidth = strokeWidth;
        this.sidePadding = (width - iconWidth) / 2;
        this.topPadding = (height - 5 * dip3) / 2;

        initPaint(color);
        initAnimations((int) transformDuration, (int) pressedDuration);

        materialMenuState = new MaterialMenuState();
    }

    private void initPaint(int color) {
        iconPaint.setAntiAlias(true);
        iconPaint.setStyle(Style.STROKE);
        iconPaint.setStrokeWidth(strokeWidth);
        iconPaint.setColor(color);

        circlePaint.setAntiAlias(true);
        circlePaint.setStyle(Style.FILL);
        circlePaint.setColor(color);
        circlePaint.setAlpha(DEFAULT_CIRCLE_ALPHA);

        setBounds(0, 0, width, height);
    }

    /*
     * Drawing
     */

    @Override public void draw(Canvas canvas) {
        final float ratio = transformationValue <= 1 ? transformationValue : 2 - transformationValue;

        if (rtlEnabled) {
            canvas.save();
            canvas.scale(-1, 1, 0, 0);
            canvas.translate(-getIntrinsicWidth(), 0);
        }

        drawTopLine(canvas, ratio);
        drawMiddleLine(canvas, ratio);
        drawBottomLine(canvas, ratio);

        if (rtlEnabled) {
            canvas.restore();
        }

        if (drawTouchCircle) drawTouchCircle(canvas);
    }

    private void drawTouchCircle(Canvas canvas) {
        canvas.restore();
        canvas.drawCircle(width / 2, height / 2, pressedProgressValue, circlePaint);
    }

    private void drawMiddleLine(Canvas canvas, float ratio) {
        canvas.restore();
        canvas.save();

        float rotation = 0;
        float pivotX = width / 2;
        float pivotY = width / 2;
        float startX = sidePadding;
        float startY = topPadding + dip3 / 2 * 5;
        float stopX = width - sidePadding;
        float stopY = topPadding + dip3 / 2 * 5;
        int alpha = 255;

        switch (animationState) {
            case BURGER_ARROW:
                // rotate by 180
                if (isMorphingForward()) {
                    rotation = ratio * ARROW_MID_LINE_ANGLE;
                } else {
                    rotation = ARROW_MID_LINE_ANGLE + (1 - ratio) * ARROW_MID_LINE_ANGLE;
                }
                // shorten one end
                stopX -= ratio * resolveStrokeModifier(ratio) / 2;
                break;
            case BURGER_X:
                // fade out
                alpha = (int) ((1 - ratio) * 255);
                break;
            case ARROW_X:
                // fade out and shorten one end
                alpha = (int) ((1 - ratio) * 255);
                startX += (1 - ratio) * dip2;
                break;
            case ARROW_CHECK:
                if (isMorphingForward()) {
                    // rotate until required angle
                    rotation = ratio * CHECK_MIDDLE_ANGLE;
                } else {
                    // rotate back to starting angle
                    rotation = CHECK_MIDDLE_ANGLE - CHECK_MIDDLE_ANGLE * (1 - ratio);
                }
                // shorten one end and lengthen the other
                startX += dip3 / 2 + dip4 - (1 - ratio) * dip2;
                stopX += ratio * dip1;
                pivotX = width / 2 + dip3 + diph;
                break;
            case BURGER_CHECK:
                // rotate until required angle
                rotation = ratio * CHECK_MIDDLE_ANGLE;
                // lengthen both ends
                startX += ratio * (dip4 + dip3 / 2);
                stopX += ratio * dip1;
                pivotX = width / 2 + dip3 + diph;
                break;
            case X_CHECK:
                // fade in
                alpha = (int) (ratio * 255);
                // rotation to check angle
                rotation = ratio * CHECK_MIDDLE_ANGLE;
                // lengthen both ends
                startX += ratio * (dip4 + dip3 / 2);
                stopX += ratio * dip1;
                pivotX = width / 2 + dip3 + diph;
                break;
        }

        iconPaint.setAlpha(alpha);
        canvas.rotate(rotation, pivotX, pivotY);
        canvas.drawLine(startX, startY, stopX, stopY, iconPaint);
        iconPaint.setAlpha(255);
    }

    private void drawTopLine(Canvas canvas, float ratio) {
        canvas.save();

        float rotation = 0, pivotX = 0, pivotY = 0;
        float rotation2 = 0;
        // pivot at center of line
        float pivotX2 = width / 2 + dip3 / 2;
        float pivotY2 = topPadding + dip2;

        float startX = sidePadding;
        float startY = topPadding + dip2;
        float stopX = width - sidePadding;
        float stopY = topPadding + dip2;
        int alpha = 255;

        switch (animationState) {
            case BURGER_ARROW:
                if (isMorphingForward()) {
                    // rotate until required angle
                    rotation = ratio * ARROW_BOT_LINE_ANGLE;
                } else {
                    // rotate back to start doing a 360
                    rotation = ARROW_BOT_LINE_ANGLE + (1 - ratio) * ARROW_TOP_LINE_ANGLE;
                }
                // rotate by middle
                pivotX = width / 2;
                pivotY = height / 2;

                // shorten both ends
                stopX -= resolveStrokeModifier(ratio);
                startX += dip3 * ratio;

                break;
            case BURGER_X:
                // rotate until required angles
                rotation = X_TOP_LINE_ANGLE * ratio;
                rotation2 = X_ROTATION_ANGLE * ratio;

                // pivot at left corner of line
                pivotX = sidePadding + dip4;
                pivotY = topPadding + dip3;

                // shorten one end
                startX += dip3 * ratio;
                break;
            case ARROW_X:
                // rotate from ARROW angle to X angle
                rotation = ARROW_BOT_LINE_ANGLE + (X_TOP_LINE_ANGLE - ARROW_BOT_LINE_ANGLE) * ratio;
                rotation2 = X_ROTATION_ANGLE * ratio;

                // move pivot from ARROW pivot to X pivot
                pivotX = width / 2 + (sidePadding + dip4 - width / 2) * ratio;
                pivotY = height / 2 + (topPadding + dip3 - height / 2) * ratio;

                // lengthen both ends
                stopX -= resolveStrokeModifier(ratio);
                startX += dip3;
                break;
            case ARROW_CHECK:
                // fade out
                alpha = (int) ((1 - ratio) * 255);
                // retain starting arrow configuration
                rotation = ARROW_BOT_LINE_ANGLE;
                pivotX = width / 2;
                pivotY = height / 2;

                // shorted both ends
                stopX -= resolveStrokeModifier(1);
                startX += dip3;
                break;
            case BURGER_CHECK:
                // fade out
                alpha = (int) ((1 - ratio) * 255);
                break;
            case X_CHECK:
                // retain X configuration
                rotation = X_TOP_LINE_ANGLE;
                rotation2 = X_ROTATION_ANGLE;
                pivotX = sidePadding + dip4;
                pivotY = topPadding + dip3;
                stopX += dip3 - dip3 * (1 - ratio);
                startX += dip3;

                // fade out
                alpha = (int) ((1 - ratio) * 255);
                break;
        }

        iconPaint.setAlpha(alpha);
        canvas.rotate(rotation, pivotX, pivotY);
        canvas.rotate(rotation2, pivotX2, pivotY2);
        canvas.drawLine(startX, startY, stopX, stopY, iconPaint);
        iconPaint.setAlpha(255);
    }

    private void drawBottomLine(Canvas canvas, float ratio) {
        canvas.restore();
        canvas.save();

        float rotation = 0, pivotX = 0, pivotY = 0;
        float rotation2 = 0;
        // pivot at center of line
        float pivotX2 = width / 2 + dip3 / 2;
        float pivotY2 = height - topPadding - dip2;

        float startX = sidePadding;
        float startY = height - topPadding - dip2;
        float stopX = width - sidePadding;
        float stopY = height - topPadding - dip2;

        switch (animationState) {
            case BURGER_ARROW:
                if (isMorphingForward()) {
                    // rotate to required angle
                    rotation = ARROW_TOP_LINE_ANGLE * ratio;
                } else {
                    // rotate back to start doing a 360
                    rotation = ARROW_TOP_LINE_ANGLE + (1 - ratio) * ARROW_BOT_LINE_ANGLE;
                }
                // pivot center of canvas
                pivotX = width / 2;
                pivotY = height / 2;

                // shorten both ends
                stopX = width - sidePadding - resolveStrokeModifier(ratio);
                startX = sidePadding + dip3 * ratio;
                break;
            case BURGER_X:
                if (isMorphingForward()) {
                    // rotate around
                    rotation2 = -X_ROTATION_ANGLE * ratio;
                } else {
                    // rotate directly
                    rotation2 = X_ROTATION_ANGLE * ratio;
                }
                // rotate to required angle
                rotation = X_BOT_LINE_ANGLE * ratio;

                // pivot left corner of line
                pivotX = sidePadding + dip4;
                pivotY = height - topPadding - dip3;

                // shorten one end
                startX += dip3 * ratio;
                break;
            case ARROW_X:
                // rotate from ARROW angle to X angle
                rotation = ARROW_TOP_LINE_ANGLE + (360 + X_BOT_LINE_ANGLE - ARROW_TOP_LINE_ANGLE) * ratio;
                rotation2 = -X_ROTATION_ANGLE * ratio;

                // move pivot from ARROW pivot to X pivot
                pivotX = width / 2 + (sidePadding + dip4 - width / 2) * ratio;
                pivotY = height / 2 + (height / 2 - topPadding - dip3) * ratio;

                // lengthen both ends
                stopX -= resolveStrokeModifier(ratio);
                startX += dip3;
                break;
            case ARROW_CHECK:
                // rotate from ARROW angle to CHECK angle
                rotation = ARROW_TOP_LINE_ANGLE + ratio * CHECK_BOTTOM_ANGLE;

                // move pivot from ARROW pivot to CHECK pivot
                pivotX = width / 2 + dip3 * ratio;
                pivotY = height / 2 - dip3 * ratio;

                // length stays same as ARROW
                stopX -= resolveStrokeModifier(1);
                startX += dip3 + (dip4 + dip1) * ratio;
                break;
            case BURGER_CHECK:
                // rotate from ARROW angle to CHECK angle
                rotation = ratio * (CHECK_BOTTOM_ANGLE + ARROW_TOP_LINE_ANGLE);

                // move pivot from BURGER pivot to CHECK pivot
                pivotX = width / 2 + dip3 * ratio;
                pivotY = height / 2 - dip3 * ratio;

                // length stays same as BURGER
                startX += dip8 * ratio;
                stopX -= resolveStrokeModifier(ratio);
                break;
            case X_CHECK:
                // rotate from X to CHECK angles
                rotation2 = -X_ROTATION_ANGLE * (1 - ratio);
                rotation = X_BOT_LINE_ANGLE + (CHECK_BOTTOM_ANGLE + ARROW_TOP_LINE_ANGLE - X_BOT_LINE_ANGLE) * ratio;

                // move pivot from X to CHECK
                pivotX = sidePadding + dip4 + (width / 2 + dip3 - sidePadding - dip4) * ratio;
                pivotY = height - topPadding - dip3 + (topPadding + height / 2 - height) * ratio;

                // shorten both ends
                startX += dip8 - (dip4 + dip1) * (1 - ratio);
                stopX -= resolveStrokeModifier(1 - ratio);
                break;
        }

        canvas.rotate(rotation, pivotX, pivotY);
        canvas.rotate(rotation2, pivotX2, pivotY2);
        canvas.drawLine(startX, startY, stopX, stopY, iconPaint);
    }

    private boolean isMorphingForward() {
        return transformationValue <= TRANSFORMATION_MID;
    }

    private float resolveStrokeModifier(float ratio) {
        switch (stroke) {
            case REGULAR:
                if (animationState == AnimationState.ARROW_X || animationState == AnimationState.X_CHECK) {
                    return dip3 - (dip3 * ratio);
                }
                return ratio * dip3;
            case THIN:
                if (animationState == AnimationState.ARROW_X || animationState == AnimationState.X_CHECK) {
                    return dip3 + diph - (dip3 + diph) * ratio;
                }
                return ratio * (dip3 + diph);
            case EXTRA_THIN:
                if (animationState == AnimationState.ARROW_X || animationState == AnimationState.X_CHECK) {
                    return dip4 - ((dip3 + dip1) * ratio);
                }
                return ratio * dip4;
        }
        return 0;
    }

    @Override public void setAlpha(int alpha) {
        iconPaint.setAlpha(alpha);
    }

    @Override public void setColorFilter(ColorFilter cf) {
        iconPaint.setColorFilter(cf);
    }

    @Override public int getOpacity() {
        return PixelFormat.TRANSPARENT;
    }

    /*
     * Accessor methods
     */

    public void setColor(int color) {
        iconPaint.setColor(color);
        circlePaint.setColor(color);
        invalidateSelf();
    }

    public void setTransformationDuration(int duration) {
        transformation.setDuration(duration);
    }

    public void setPressedDuration(int duration) {
        pressedCircle.setDuration(duration);
    }

    public void setInterpolator(Interpolator interpolator) {
        transformation.setInterpolator(interpolator);
    }

    public void setNeverDrawTouch(boolean neverDrawTouch) {
        this.neverDrawTouch = neverDrawTouch;
    }

    public void setIconState(IconState iconState) {
        synchronized (lock) {
            if (transformationRunning) {
                transformation.cancel();
                transformationRunning = false;
            }

            if (currentIconState == iconState) return;

            switch (iconState) {
                case BURGER:
                    animationState = AnimationState.BURGER_ARROW;
                    transformationValue = TRANSFORMATION_START;
                    break;
                case ARROW:
                    animationState = AnimationState.BURGER_ARROW;
                    transformationValue = TRANSFORMATION_MID;
                    break;
                case X:
                    animationState = AnimationState.BURGER_X;
                    transformationValue = TRANSFORMATION_MID;
                    break;
                case CHECK:
                    animationState = AnimationState.BURGER_CHECK;
                    transformationValue = TRANSFORMATION_MID;
            }
            currentIconState = iconState;
            invalidateSelf();
        }
    }

    public void animateIconState(IconState state, boolean drawTouch) {
        synchronized (lock) {
            if (transformationRunning) {
                transformation.end();
                pressedCircle.end();
            }
            drawTouchCircle = drawTouch;
            animatingIconState = state;
            start();
        }
    }

    public IconState setTransformationOffset(AnimationState animationState, float offset) {
        if (offset < TRANSFORMATION_START || offset > TRANSFORMATION_END) {
            throw new IllegalArgumentException(
                String.format("Value must be between %s and %s", TRANSFORMATION_START, TRANSFORMATION_END)
            );
        }

        this.animationState = animationState;

        final boolean isFirstIcon = offset < TRANSFORMATION_MID || offset == TRANSFORMATION_END;

        currentIconState = isFirstIcon ? animationState.getFirstState() : animationState.getSecondState();
        animatingIconState = isFirstIcon ? animationState.getSecondState() : animationState.getFirstState();

        setTransformationValue(offset);

        return currentIconState;
    }

    public void setRTLEnabled(boolean rtlEnabled) {
        this.rtlEnabled = rtlEnabled;
        invalidateSelf();
    }

    public IconState getIconState() {
        return currentIconState;
    }

    /*
     * Animations
     */
    private Property<MaterialMenuDrawable, Float> transformationProperty
        = new Property<MaterialMenuDrawable, Float>(Float.class, "transformation") {
        @Override
        public Float get(MaterialMenuDrawable object) {
            return object.getTransformationValue();
        }

        @Override
        public void set(MaterialMenuDrawable object, Float value) {
            object.setTransformationValue(value);
        }
    };

    private Property<MaterialMenuDrawable, Float> pressedProgressProperty
        = new Property<MaterialMenuDrawable, Float>(Float.class, "pressedProgress") {
        @Override
        public Float get(MaterialMenuDrawable object) {
            return object.getPressedProgress();
        }

        @Override
        public void set(MaterialMenuDrawable object, Float value) {
            object.setPressedProgress(value);
        }
    };

    public Float getTransformationValue() {
        return transformationValue;
    }

    public void setTransformationValue(Float value) {
        this.transformationValue = value;
        invalidateSelf();
    }

    public Float getPressedProgress() {
        return pressedProgressValue;
    }

    public void setPressedProgress(Float value) {
        this.pressedProgressValue = value;
        circlePaint.setAlpha((int) (DEFAULT_CIRCLE_ALPHA * (1 - value / (circleRadius * 1.22f))));
        invalidateSelf();
    }

    private void initAnimations(int transformDuration, int pressedDuration) {
        transformation = ObjectAnimator.ofFloat(this, transformationProperty, 0);
        transformation.setInterpolator(new DecelerateInterpolator(3));
        transformation.setDuration(transformDuration);
        transformation.addListener(new AnimatorListenerAdapter() {
            @Override public void onAnimationEnd(Animator animation) {
                transformationRunning = false;
                setIconState(animatingIconState);
            }
        });


        pressedCircle = ObjectAnimator.ofFloat(this, pressedProgressProperty, 0, 0);
        pressedCircle.setDuration(pressedDuration);
        pressedCircle.setInterpolator(new DecelerateInterpolator());
        pressedCircle.addListener(new AnimatorListenerAdapter() {
            @Override public void onAnimationEnd(Animator animation) {
                pressedProgressValue = 0;
            }

            @Override public void onAnimationCancel(Animator animation) {
                pressedProgressValue = 0;
            }
        });
    }

    private boolean resolveTransformation() {
        boolean isCurrentBurger = currentIconState == IconState.BURGER;
        boolean isCurrentArrow = currentIconState == IconState.ARROW;
        boolean isCurrentX = currentIconState == IconState.X;
        boolean isCurrentCheck = currentIconState == IconState.CHECK;
        boolean isAnimatingBurger = animatingIconState == IconState.BURGER;
        boolean isAnimatingArrow = animatingIconState == IconState.ARROW;
        boolean isAnimatingX = animatingIconState == IconState.X;
        boolean isAnimatingCheck = animatingIconState == IconState.CHECK;

        if ((isCurrentBurger && isAnimatingArrow) || (isCurrentArrow && isAnimatingBurger)) {
            animationState = AnimationState.BURGER_ARROW;
            return isCurrentBurger;
        }

        if ((isCurrentArrow && isAnimatingX) || (isCurrentX && isAnimatingArrow)) {
            animationState = AnimationState.ARROW_X;
            return isCurrentArrow;
        }

        if ((isCurrentBurger && isAnimatingX) || (isCurrentX && isAnimatingBurger)) {
            animationState = AnimationState.BURGER_X;
            return isCurrentBurger;
        }

        if ((isCurrentArrow && isAnimatingCheck) || (isCurrentCheck && isAnimatingArrow)) {
            animationState = AnimationState.ARROW_CHECK;
            return isCurrentArrow;
        }

        if ((isCurrentBurger && isAnimatingCheck) || (isCurrentCheck && isAnimatingBurger)) {
            animationState = AnimationState.BURGER_CHECK;
            return isCurrentBurger;
        }

        if ((isCurrentX && isAnimatingCheck) || (isCurrentCheck && isAnimatingX)) {
            animationState = AnimationState.X_CHECK;
            return isCurrentX;
        }

        throw new IllegalStateException(
            String.format("Animating from %s to %s is not supported", currentIconState, animatingIconState)
        );
    }

    @Override public void start() {
        if (transformationRunning || animatingIconState == null || animatingIconState == currentIconState) return;
        transformationRunning = true;

        final boolean direction = resolveTransformation();
        transformation.setFloatValues(
            direction ? TRANSFORMATION_START : TRANSFORMATION_MID,
            direction ? TRANSFORMATION_MID : TRANSFORMATION_END
        );
        transformation.start();

        if (pressedCircle.isRunning()) {
            pressedCircle.cancel();
        }
        if (drawTouchCircle && !neverDrawTouch) {
            pressedCircle.setFloatValues(0, circleRadius * 1.22f);
            pressedCircle.start();
        }

        invalidateSelf();
    }

    @Override public void stop() {
        if (isRunning() && transformation.isRunning()) {
            transformation.end();
        } else {
            transformationRunning = false;
            invalidateSelf();
        }
    }

    @Override public boolean isRunning() {
        return transformationRunning;
    }

    @Override
    public int getIntrinsicWidth() {
        return width;
    }

    @Override
    public int getIntrinsicHeight() {
        return height;
    }


    @Override
    public ConstantState getConstantState() {
        materialMenuState.changingConfigurations = getChangingConfigurations();
        return materialMenuState;
    }

    @Override
    public Drawable mutate() {
        materialMenuState = new MaterialMenuState();
        return this;
    }

    private final class MaterialMenuState extends ConstantState {
        private int changingConfigurations;

        private MaterialMenuState() {
        }

        @Override
        public Drawable newDrawable() {
            MaterialMenuDrawable drawable = new MaterialMenuDrawable(
                circlePaint.getColor(), stroke, transformation.getDuration(),
                pressedCircle.getDuration(), width, height, iconWidth, circleRadius, strokeWidth, dip1
            );
            drawable.setIconState(animatingIconState != null ? animatingIconState : currentIconState);
            drawable.setRTLEnabled(rtlEnabled);
            return drawable;
        }

        @Override
        public int getChangingConfigurations() {
            return changingConfigurations;
        }
    }

    static float dpToPx(Resources resources, float dp) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, resources.getDisplayMetrics());
    }
}




Java Source Code List

com.balysv.materialmenu.MaterialMenuBase.java
com.balysv.materialmenu.MaterialMenuDrawable.java
com.balysv.materialmenu.MaterialMenuIcon.java
com.balysv.materialmenu.MaterialMenuView.java
com.balysv.materialmenu.MaterialMenu.java
com.balysv.materialmenu.demo.BaseActivityHelper.java
com.balysv.materialmenu.demo.abc.BaseActivity.java
com.balysv.materialmenu.demo.abc.CustomViewActivity.java
com.balysv.materialmenu.demo.abc.HomeIconActivity.java
com.balysv.materialmenu.demo.abs.BaseActivity.java
com.balysv.materialmenu.demo.abs.CustomViewActivity.java
com.balysv.materialmenu.demo.abs.HomeIconActivity.java
com.balysv.materialmenu.demo.stock.BaseActivity.java
com.balysv.materialmenu.demo.stock.CustomViewActivity.java
com.balysv.materialmenu.demo.stock.HomeIconActivity.java
com.balysv.materialmenu.demo.toolbar.ToolbarActivity.java
com.balysv.materialmenu.extras.abc.MaterialMenuIconCompat.java
com.balysv.materialmenu.extras.abs.MaterialMenuIconSherlock.java
com.balysv.materialmenu.extras.toolbar.MaterialMenuIconToolbar.java