Android 从中心按钮中弹出多个按钮

示例:

  

java代码:

package com.chy.mytestmap;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.RadialGradient;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AnticipateInterpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.OvershootInterpolator;

import androidx.core.graphics.ColorUtils;

import java.util.ArrayList;
import java.util.List;

public class CircleMenuView extends View {

    private static final String TAG = "CircleMenuView";

    private static final int STATUS_MENU_OPEN = 1;

    private static final int STATUS_MENU_OPENED = 1 << 1;

    private static final int STATUS_MENU_CLOSE = 1 << 2;

    private static final int STATUS_MENU_CLOSE_CLEAR = 1 << 3;

    private static final int STATUS_MENU_CLOSED = 1 << 4;

    private static final int STATUS_MENU_CANCEL = 1 << 5;

    /**
     * 最多8个菜单
     */
    private static final int MAX_SUBMENU_NUM = 8;

    private final int shadowRadius = 5;

    private int partSize;

    private int iconSize;

    private float circleMenuRadius;

    private int itemNum;

    private float itemMenuRadius;

    private float fraction, rFraction;

    private float pathLength;

    private int mainMenuColor;

    private Drawable openMenuIconDrawable, closeMenuIconDrawable;

    private List<Integer> subMenuColorList;

    private List<Drawable> subMenuDrawableList;

    private List<RectF> menuRectFList;

    private int centerX, centerY;

    private int clickIndex;

    private int rotateAngle;

    private int itemIconSize;

    private int pressedColor;

    /**
     * view此时的状态
     */
    private int state;

    private boolean pressed;

    private Paint oPaint, cPaint, sPaint;

    private PathMeasure pathMeasure;

    private Path path, dstPath;

    private OnCircleMenuFeedbackListener mFeedbackListener;

    public CircleMenuView(Context context) {
        this(context, null);
    }

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

    public CircleMenuView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        state = STATUS_MENU_CLOSED;
        init();

        setFocusable(true);
        setFocusableInTouchMode(true);
    }

    private void init() {
        initTool();

        mainMenuColor = Color.parseColor("#CDCDCD");

        openMenuIconDrawable = new GradientDrawable();
        closeMenuIconDrawable = new GradientDrawable();

        subMenuColorList = new ArrayList<>();
        subMenuDrawableList = new ArrayList<>();
        menuRectFList = new ArrayList<>();

    }

    private void initTool() {
        oPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        oPaint.setStyle(Paint.Style.FILL_AND_STROKE);

        cPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        cPaint.setStyle(Paint.Style.STROKE);
        // 设置画笔头部和尾部的形状
        cPaint.setStrokeCap(Paint.Cap.ROUND);

        sPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        sPaint.setStyle(Paint.Style.FILL);

        path = new Path();
        dstPath = new Path();
        pathMeasure = new PathMeasure();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);

        int measureWidthSize = width, measureHeightSize = height;

        if (widthMode == MeasureSpec.AT_MOST) {
            measureWidthSize = dip2px(20) * 10;
        }

        if (heightMode == MeasureSpec.AT_MOST) {
            measureHeightSize = dip2px(20) * 10;
        }
        setMeasuredDimension(measureWidthSize, measureHeightSize);
        // super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        int minSize = Math.min(getMeasuredWidth(), getMeasuredHeight());

        // 中心图标所占区域大小
        partSize = minSize / 10;
        // 子菜单图标大小
        iconSize = partSize * 4 / 5;
        circleMenuRadius = partSize * 3;

        centerX = getMeasuredWidth() / 2;
        centerY = getMeasuredHeight() / 2;

        // 设置中心图标drawable大小
        resetMainDrawableBounds();

        // 关闭的路径设置,顺时针绘制
        path.addCircle(centerX, centerY, circleMenuRadius, Path.Direction.CW);
        // 闭环
        pathMeasure.setPath(path, true);
        pathLength = pathMeasure.getLength();

        RectF mainMenuRectF = new RectF(
                centerX - partSize,
                centerY - partSize,
                centerX + partSize,
                centerY + partSize);
        menuRectFList.add(mainMenuRectF);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        switch (state) {
            case STATUS_MENU_CLOSED:
                drawMainMenu(canvas);
                break;
            case STATUS_MENU_OPEN:
                drawMainMenu(canvas);
                drawSubMenu(canvas);
                break;
            case STATUS_MENU_OPENED:
                drawMainMenu(canvas);
                drawSubMenu(canvas);
                break;
            case STATUS_MENU_CLOSE:
                drawMainMenu(canvas);
                drawSubMenu(canvas);
                drawCircleMenu(canvas);
                break;
            case STATUS_MENU_CLOSE_CLEAR:
                drawMainMenu(canvas);
                drawCircleMenu(canvas);
                break;
            case STATUS_MENU_CANCEL:
                drawMainMenu(canvas);
                drawSubMenu(canvas);
                break;
        }
    }

    /**
     * 绘制周围子菜单环绕的圆环路径
     *
     * @param canvas
     */
    private void drawCircleMenu(Canvas canvas) {
        if (state == STATUS_MENU_CLOSE) {
            drawCirclePath(canvas);
            drawCircleIcon(canvas);
        } else {
            cPaint.setStrokeWidth(partSize * 2 + partSize * .5f * fraction);
            cPaint.setColor(calcAlphaColor(getClickMenuColor(), true));
            canvas.drawCircle(centerX, centerY, circleMenuRadius + partSize * .5f * fraction, cPaint);
        }
    }

    private int getClickMenuColor() {
        return clickIndex == 0 ? mainMenuColor : subMenuColorList.get(clickIndex - 1);
    }

    /**
     * 绘制子菜单转动时的图标
     *
     * @param canvas
     */
    private void drawCircleIcon(Canvas canvas) {
        canvas.save();
        Drawable selDrawable = subMenuDrawableList.get(clickIndex - 1);
        if (selDrawable == null) return;
        int startAngle = (clickIndex - 1) * (360 / itemNum);
        int endAngle = 360 + startAngle;
        int itemX = (int) (centerX + Math.sin(Math.toRadians((endAngle - startAngle) * fraction + startAngle)) * circleMenuRadius);
        int itemY = (int) (centerY - Math.cos(Math.toRadians((endAngle - startAngle) * fraction + startAngle)) * circleMenuRadius);
        canvas.rotate(360 * fraction, itemX, itemY);
        selDrawable.setBounds(itemX - iconSize / 2,
                itemY - iconSize / 2,
                itemX + iconSize / 2,
                itemY + iconSize / 2);
        selDrawable.draw(canvas);
        canvas.restore();
    }

    /**
     * 绘制子菜单项转动时的轨迹路径
     *
     * @param canvas
     */
    private void drawCirclePath(Canvas canvas) {
        canvas.save();
        // 旋转画布,旋转到点击子菜单的位置
        Log.d(TAG, "drawCirclePath: rotateAngle = " + rotateAngle);
        canvas.rotate(rotateAngle, centerX, centerY);
        dstPath.reset();
        dstPath.lineTo(0, 0);
        pathMeasure.getSegment(0, pathLength * fraction, dstPath, true);
        cPaint.setStrokeWidth(partSize * 2);
        cPaint.setColor(getClickMenuColor());
        canvas.drawPath(dstPath, cPaint);
        canvas.restore();
    }

    /**
     * 绘制周围子菜单项按钮
     *
     * @param canvas
     */
    private void drawSubMenu(Canvas canvas) {
        int itemX, itemY, angle;
        final float offsetRadius = 1.5f;
        RectF menuRectF;
        for (int i = 0; i < itemNum; i++) {
            //  angle = i * (360 / itemNum);// 按钮环绕中心按钮
            angle = i * (-240 / itemNum);// 按钮在左侧

            // 确定itemX itemY
            if (state == STATUS_MENU_OPEN) {
                Log.d(TAG, "drawSubMenu: ");
                itemX = (int) (centerX + Math.sin(Math.toRadians(angle)) * (circleMenuRadius - (1 - fraction) * partSize * offsetRadius));
                itemY = (int) (centerY - Math.cos(Math.toRadians(angle)) * (circleMenuRadius - (1 - fraction) * partSize * offsetRadius));
                oPaint.setColor(calcAlphaColor(subMenuColorList.get(i), false));
                sPaint.setColor(calcAlphaColor(subMenuColorList.get(i), false));
            } else if (state == STATUS_MENU_CANCEL) {
                itemX = (int) (centerX + Math.sin(Math.toRadians(angle)) * (circleMenuRadius - fraction * partSize * offsetRadius));
                itemY = (int) (centerY - Math.cos(Math.toRadians(angle)) * (circleMenuRadius - fraction * partSize * offsetRadius));
                oPaint.setColor(calcAlphaColor(subMenuColorList.get(i), true));
                sPaint.setColor(calcAlphaColor(subMenuColorList.get(i), true));
            } else {
                itemX = (int) (centerX + Math.sin(Math.toRadians(angle)) * circleMenuRadius);
                itemY = (int) (centerY - Math.cos(Math.toRadians(angle)) * circleMenuRadius);
                oPaint.setColor(subMenuColorList.get(i));
                sPaint.setColor(subMenuColorList.get(i));
            }

            if (pressed && clickIndex - 1 == i) {
                oPaint.setColor(pressedColor);
            }

            drawMenuShadow(canvas, itemX, itemY, itemMenuRadius);

            canvas.drawCircle(itemX, itemY, itemMenuRadius, oPaint);

            drawSubMenuIcon(canvas, itemX, itemY, i);

            menuRectF = new RectF(itemX - partSize,
                    itemY - partSize,
                    itemX + partSize,
                    itemY + partSize);

            if (menuRectFList.size() - 1 > i) {
                menuRectFList.remove(i + 1);
            }

            menuRectFList.add(i + 1, menuRectF);
        }
    }

    /**
     * 绘制子菜单项图标
     *
     * @param canvas
     * @param centerX
     * @param centerY
     * @param index
     */
    private void drawSubMenuIcon(Canvas canvas, int centerX, int centerY, int index) {
        // 子菜单图标drawable大小,为子菜单大小的一半
        int diff;
        if (state == STATUS_MENU_OPEN || state == STATUS_MENU_CANCEL) {
            diff = itemIconSize / 2;
        } else {
            diff = iconSize / 2;
        }
        resetBoundsAndDrawIcon(canvas, subMenuDrawableList.get(index), centerX, centerY, diff);
    }

    private void resetBoundsAndDrawIcon(Canvas canvas, Drawable drawable, int centerX, int centerY, int diff) {
        if (drawable == null) return;
        drawable.setBounds(centerX - diff, centerY - diff, centerX + diff, centerY + diff);
        drawable.draw(canvas);
    }

    /**
     * 绘制中间的菜单开关按钮
     *
     * @param canvas
     */
    private void drawMainMenu(Canvas canvas) {
        float centerMenuRadius, realFraction;
        if (state == STATUS_MENU_CLOSE) {
            // 中心主菜单按钮以两倍速度缩小
            realFraction = (1 - fraction * 2) == 0 ? 0 : (1 - fraction * 2);
            centerMenuRadius = partSize * realFraction;
        } else if (state == STATUS_MENU_CLOSE_CLEAR) {
            // 中心主菜单按钮以四倍速度扩大
            realFraction = fraction * 4 >= 1 ? 1 : fraction * 4;
            centerMenuRadius = partSize * realFraction;
        } else if (state == STATUS_MENU_CLOSED || state == STATUS_MENU_CANCEL) {
            centerMenuRadius = partSize;
        } else {
            centerMenuRadius = partSize;
        }

        if (state == STATUS_MENU_OPEN || state == STATUS_MENU_OPENED || state == STATUS_MENU_CLOSE) {
            // 菜单已经打开了,然后点击中间图标时按压的颜色
            //oPaint.setColor(calcPressedEffectColor(0, .5f));
        } else if (pressed && clickIndex == 0) {
            // 设置点击中间按钮的按压颜色
            //oPaint.setColor(pressedColor);
        } else {
            oPaint.setColor(mainMenuColor);
            sPaint.setColor(mainMenuColor);
        }

        drawMenuShadow(canvas, centerX, centerY, centerMenuRadius);

        // 绘制中间图标
        canvas.drawCircle(centerX, centerY, centerMenuRadius, oPaint);
        drawMainMenuIcon(canvas);
    }

    private void drawMainMenuIcon(Canvas canvas) {
        canvas.save();
        switch (state) {
            case STATUS_MENU_CLOSED:
                if (openMenuIconDrawable != null)
                    openMenuIconDrawable.draw(canvas);
                break;
            case STATUS_MENU_OPEN:
                canvas.rotate(45 * (fraction - 1), centerX, centerY);
                resetBoundsAndDrawIcon(canvas, closeMenuIconDrawable, centerX, centerY, iconSize / 2);
                break;
            case STATUS_MENU_OPENED:
                resetBoundsAndDrawIcon(canvas, closeMenuIconDrawable, centerX, centerY, iconSize / 2);
                break;
            case STATUS_MENU_CLOSE:
                resetBoundsAndDrawIcon(canvas, closeMenuIconDrawable, centerX, centerY, itemIconSize / 2);
                break;
            case STATUS_MENU_CLOSE_CLEAR:
                canvas.rotate(90 * (rFraction - 1), centerX, centerY);
                resetBoundsAndDrawIcon(canvas, openMenuIconDrawable, centerX, centerY, itemIconSize / 2);
                break;
            case STATUS_MENU_CANCEL:
                canvas.rotate(-45 * fraction, centerX, centerY);
                if (closeMenuIconDrawable != null)
                    closeMenuIconDrawable.draw(canvas);
                break;
        }
        canvas.restore();
    }

    /**
     * 绘制菜单按钮阴影
     *
     * @param canvas
     * @param centerX
     * @param centerY
     */
    private void drawMenuShadow(Canvas canvas, int centerX, int centerY, float radius) {
        if (radius + shadowRadius > 0) {
            sPaint.setShader(new RadialGradient(centerX, centerY, radius + shadowRadius,
                    Color.BLACK, Color.TRANSPARENT, Shader.TileMode.CLAMP));
            canvas.drawCircle(centerX, centerY, radius + shadowRadius, sPaint);
        }
    }

    /**
     * 原版本
     * */
    /*@Override
    public boolean onTouchEvent(MotionEvent event) {
        if (state == STATUS_MENU_CLOSE || state == STATUS_MENU_CLOSE_CLEAR) return true;
        int index = clickWhichRectF(event.getX(), event.getY());
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                pressed = true;
                if (index != -1) {
                    clickIndex = index;
                    updatePressEffect(index, pressed);
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (index == -1) {
                    pressed = false;
                    invalidate();
                }
                break;
            case MotionEvent.ACTION_UP:
                pressed = false;
                if (index != -1) {
                    clickIndex = index;
                    updatePressEffect(index, pressed);
                }
                if (index == 0) {
                    // 点击的是中间的按钮
                    if (state == STATUS_MENU_CLOSED) {
                        state = STATUS_MENU_OPEN;
                        if (mFeedbackListener != null) {
                            mFeedbackListener.onMenuCenterClicked();
                        }
                        startOpenMenuAnima();
                    } else if (state == STATUS_MENU_OPENED) {
                        state = STATUS_MENU_CANCEL;
                        if (mFeedbackListener != null) {
                            mFeedbackListener.onMenuCenterClicked();
                        }
                        startCancelMenuAnima();
                    }
                } else {
                    // 点击的是周围子菜单项按钮
                    if (state == STATUS_MENU_OPENED && index != -1) {
                        state = STATUS_MENU_CLOSE;
                        if (mFeedbackListener != null) {
                            mFeedbackListener.onMenuSelected(index - 1);
                        }
                        // 这里最后再减去90度的原因是:画布坐标系是普通数学坐标系顺时针旋转90度得到的
                        // 不减去90度的话则会从画布坐标系的第一象限算起
                        rotateAngle = clickIndex * (360 / itemNum) - (360 / itemNum) - 90;
                        startCloseMeunAnima();
                    }
                }
                break;
        }
        return true;
    }*/

    /**
     * 修改版本
     * */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (state == STATUS_MENU_CLOSE || state == STATUS_MENU_CLOSE_CLEAR) return true;
        int index = clickWhichRectF(event.getX(), event.getY());
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                pressed = true;
                if (index != -1) {
                    clickIndex = index;
                    updatePressEffect(index, pressed);
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (index == -1) {
                    pressed = false;
                    invalidate();
                }
                break;
            case MotionEvent.ACTION_UP:
                pressed = false;
                if (index != -1) {
                    clickIndex = index;
                    updatePressEffect(index, pressed);
                }
                if (index == 0) {
                    // 点击的是中间的按钮
                    if (state == STATUS_MENU_CLOSED) {
                        state = STATUS_MENU_OPEN;
                        if (mFeedbackListener != null) {
                            mFeedbackListener.onMenuCenterClicked();
                        }
                        startOpenMenuAnima();
                    } else if (state == STATUS_MENU_OPENED) {
                        state = STATUS_MENU_CANCEL;
                        if (mFeedbackListener != null) {
                            mFeedbackListener.onMenuCenterClicked();
                        }
                        startCancelMenuAnima();
                    }
                } else {
                    // 点击的是周围子菜单项按钮
                    if (state == STATUS_MENU_OPENED && index != -1) {
                        state = STATUS_MENU_CANCEL;
                        if (mFeedbackListener != null) {
                            mFeedbackListener.onMenuSelected(index - 1);
                        }
                        // 这里最后再减去90度的原因是:画布坐标系是普通数学坐标系顺时针旋转90度得到的
                        // 不减去90度的话则会从画布坐标系的第一象限算起
                        //rotateAngle = clickIndex * (360 / itemNum) - (360 / itemNum) - 90;
                        startCancelMenuAnima();
                    }
                }
                break;
        }
        return true;
    }

    /**
     * 更新按钮的状态
     *
     * @param menuIndex
     * @param press
     */
    private void updatePressEffect(int menuIndex, boolean press) {
        if (press) {
            pressedColor = calcPressedEffectColor(menuIndex, .15f);
        }
        invalidate();
    }

    /**
     * 获取按钮被按下的颜色
     *
     * @param menuIndex
     * @param depth     取值范围为[0, 1].值越大,颜色越深
     * @return
     */
    private int calcPressedEffectColor(int menuIndex, float depth) {
        int color = menuIndex == 0 ? mainMenuColor : subMenuColorList.get(menuIndex - 1);
        float[] hsv = new float[3];
        Color.colorToHSV(color, hsv);
        hsv[2] *= (1.f - depth);
        return Color.HSVToColor(hsv);
    }

    /**
     * 用于完成在 View 中的圆环逐渐扩散消失的动画效果 <br/>
     * <p>
     * 根据 fraction 调整 color 的 Alpha 值
     *
     * @param color   被调整 Alpha 值的颜色
     * @param reverse true : 由不透明到透明的顺序调整,否则就逆序
     * @return
     */
    private int calcAlphaColor(int color, boolean reverse) {
        int alpha;
        if (reverse) { // 由不透明到透明
            alpha = (int) (255 * (1.f - fraction));
        } else { // 由透明到不透明
            alpha = (int) (255 * fraction);
        }
        if (alpha >= 255) alpha = 255;
        if (alpha <= 0) alpha = 0;
        return ColorUtils.setAlphaComponent(color, alpha);
    }

    /**
     * 启动打开菜单动画
     */
    private void startOpenMenuAnima() {
        ValueAnimator openAnima = ValueAnimator.ofFloat(1.f, 100.f);
        openAnima.setDuration(500);
        openAnima.setInterpolator(new OvershootInterpolator());
        openAnima.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                fraction = valueAnimator.getAnimatedFraction();
                itemMenuRadius = fraction * partSize;
                itemIconSize = (int) (fraction * iconSize);
                invalidate();
            }
        });
        openAnima.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                state = STATUS_MENU_OPENED;
                if (mFeedbackListener != null) {
                    mFeedbackListener.onMenuOpened();
                }
            }
        });
        openAnima.start();
    }

    /**
     * 启动取消动画
     */
    private void startCancelMenuAnima() {
        ValueAnimator cancelAnima = ValueAnimator.ofFloat(1.f, 100.f);
        cancelAnima.setDuration(500);
        cancelAnima.setInterpolator(new AnticipateInterpolator());
        cancelAnima.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                fraction = valueAnimator.getAnimatedFraction();
                itemMenuRadius = (1 - fraction) * partSize;
                itemIconSize = (int) ((1 - fraction) * iconSize);
                invalidate();
            }
        });
        cancelAnima.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                state = STATUS_MENU_CLOSED;
                if (mFeedbackListener != null)
                    mFeedbackListener.onMenuClosed();
            }
        });
        cancelAnima.start();
    }

    /**
     * 开启关闭菜单动画 </br>
     * <p>关闭菜单动画分为三部分</p>
     * <ur>
     * <li>选中菜单项转动一周</li>
     * <li>环状轨迹扩散消失</li>
     * <li>主菜单按钮旋转</li>
     * </ur>
     */
    private void startCloseMeunAnima() {
        // 选中菜单项转动一周动画驱动
        ValueAnimator aroundAnima = ValueAnimator.ofFloat(1.f, 100.f);
        aroundAnima.setDuration(500);
        aroundAnima.setInterpolator(new AccelerateDecelerateInterpolator());
        aroundAnima.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                fraction = valueAnimator.getAnimatedFraction();
                // 中心主菜单图标以两倍速度缩小
                float animaFraction = fraction * 2 >= 1 ? 1 : fraction * 2;
                itemIconSize = (int) ((1 - animaFraction) * iconSize);
                invalidate();
            }
        });
        aroundAnima.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                state = STATUS_MENU_CLOSE_CLEAR;
            }
        });

        // 环状轨迹扩散消失动画驱动
        ValueAnimator spreadAnima = ValueAnimator.ofFloat(1.f, 100.f);
        spreadAnima.setInterpolator(new LinearInterpolator());
        spreadAnima.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                fraction = valueAnimator.getAnimatedFraction();
            }
        });

        // 主菜单转动动画驱动
        ValueAnimator rotateAnima = ValueAnimator.ofFloat(1.f, 100.f);
        rotateAnima.setInterpolator(new OvershootInterpolator());
        rotateAnima.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                rFraction = valueAnimator.getAnimatedFraction();
                itemIconSize = (int) (rFraction * iconSize);
                invalidate();
            }
        });

        AnimatorSet closeAnimaSet = new AnimatorSet();
        closeAnimaSet.setDuration(500);
        closeAnimaSet.play(spreadAnima).with(rotateAnima);
        closeAnimaSet.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                state = STATUS_MENU_CLOSED;
                if (mFeedbackListener != null) {
                    mFeedbackListener.onMenuClosed();
                }
            }
        });

        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.play(aroundAnima).before(closeAnimaSet);
        animatorSet.start();
    }

    /**
     * 获取当前点击的是哪一个菜单按钮 <br/>
     * 中心菜单下标为0,周围菜单从正上方顺时针计数1~5
     *
     * @param x
     * @param y
     * @return
     */
    private int clickWhichRectF(float x, float y) {
        int which = -1;
        for (RectF rectF : menuRectFList) {
            if (rectF.contains(x, y)) {
                which = menuRectFList.indexOf(rectF);
                break;
            }
        }
        return which;
    }

    private Drawable convertDrawable(int iconRes) {
        return getResources().getDrawable(iconRes);
    }

    private Drawable convertBitmap(Bitmap bitmap) {
        return new BitmapDrawable(getResources(), bitmap);
    }

    private void resetMainDrawableBounds() {
        openMenuIconDrawable.setBounds(centerX - iconSize / 2, centerY - iconSize / 2,
                centerX + iconSize / 2, centerY + iconSize / 2);
        closeMenuIconDrawable.setBounds(centerX - iconSize / 2, centerY - iconSize / 2,
                centerX + iconSize / 2, centerY + iconSize / 2);
    }

    /**
     * 设置主菜单的背景色,以及打开/关闭的图标
     *
     * @param mainMenuColor 主菜单背景色
     * @param openMenuRes   菜单打开图标,Resource 格式
     * @param closeMenuRes  菜单关闭图标,Resource 格式
     * @return
     */
    public CircleMenuView setMainMenu(int mainMenuColor, int openMenuRes, int closeMenuRes) {
        openMenuIconDrawable = convertDrawable(openMenuRes);
        closeMenuIconDrawable = convertDrawable(closeMenuRes);
        this.mainMenuColor = mainMenuColor;
        return this;
    }

    /**
     * 设置主菜单的背景色,以及打开/关闭的图标
     *
     * @param mainMenuColor   主菜单背景色
     * @param openMenuBitmap  菜单打开图标,Bitmap 格式
     * @param closeMenuBitmap 菜单关闭图标,Bitmap 格式
     * @return
     */
    public CircleMenuView setMainMenu(int mainMenuColor, Bitmap openMenuBitmap, Bitmap closeMenuBitmap) {
        openMenuIconDrawable = convertBitmap(openMenuBitmap);
        closeMenuIconDrawable = convertBitmap(closeMenuBitmap);
        this.mainMenuColor = mainMenuColor;
        return this;
    }

    /**
     * 设置主菜单的背景色,以及打开/关闭的图标
     *
     * @param mainMenuColor     主菜单背景色
     * @param openMenuDrawable  菜单打开图标,Drawable 格式
     * @param closeMenuDrawable 菜单关闭图标,Drawable 格式
     * @return
     */
    public CircleMenuView setMainMenu(int mainMenuColor, Drawable openMenuDrawable, Drawable closeMenuDrawable) {
        openMenuIconDrawable = openMenuDrawable;
        closeMenuIconDrawable = closeMenuDrawable;
        this.mainMenuColor = mainMenuColor;
        return this;
    }

    /**
     * 添加一个子菜单项,包括子菜单的背景色以及图标
     *
     * @param menuColor 子菜单的背景色
     * @param menuRes   子菜单图标,Resource 格式
     * @return
     */
    public CircleMenuView addSubMenu(int menuColor, int menuRes) {
        if (subMenuColorList.size() < MAX_SUBMENU_NUM && subMenuDrawableList.size() < MAX_SUBMENU_NUM) {
            subMenuColorList.add(menuColor);
            subMenuDrawableList.add(convertDrawable(menuRes));
            itemNum = Math.min(subMenuColorList.size(), subMenuDrawableList.size());
        } else {
            throw new IllegalStateException("注意!最多添加8个子菜单!MAX_SUBMENU_NUM = 8");
        }
        return this;
    }

    /**
     * 添加一个子菜单项,包括子菜单的背景色以及图标
     *
     * @param menuColor  子菜单的背景色
     * @param menuBitmap 子菜单图标,Bitmap 格式
     * @return
     */
    public CircleMenuView addSubMenu(int menuColor, Bitmap menuBitmap) {
        if (subMenuColorList.size() < MAX_SUBMENU_NUM && subMenuDrawableList.size() < MAX_SUBMENU_NUM) {
            subMenuColorList.add(menuColor);
            subMenuDrawableList.add(convertBitmap(menuBitmap));
            itemNum = Math.min(subMenuColorList.size(), subMenuDrawableList.size());
        }
        return this;
    }

    /**
     * 添加一个子菜单项,包括子菜单的背景色以及图标
     *
     * @param menuColor    子菜单的背景色
     * @param menuDrawable 子菜单图标,Drawable 格式
     * @return
     */
    public CircleMenuView addSubMenu(int menuColor, Drawable menuDrawable) {
        if (subMenuColorList.size() < MAX_SUBMENU_NUM && subMenuDrawableList.size() < MAX_SUBMENU_NUM) {
            subMenuColorList.add(menuColor);
            subMenuDrawableList.add(menuDrawable);
            itemNum = Math.min(subMenuColorList.size(), subMenuDrawableList.size());
        }
        return this;
    }

    /**
     * 打开菜单
     * Open the CircleMenu
     */
    public void openMenu() {
        if (state == STATUS_MENU_CLOSED) {
            state = STATUS_MENU_OPEN;
            startOpenMenuAnima();
        }
    }

    /**
     * 关闭菜单
     * Close the CircleMenu
     */
    public void closeMenu() {
        if (state == STATUS_MENU_OPENED) {
            state = STATUS_MENU_CANCEL;
            startCancelMenuAnima();
        }
    }

    /**
     * 菜单是否关闭
     * Returns whether the menu is alread open
     *
     * @return
     */
    public boolean isOpened() {
        return state == STATUS_MENU_OPENED;
    }

    private int dip2px(float dpValue) {
        final float scale = getContext().getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    public CircleMenuView setFeedbackListener(OnCircleMenuFeedbackListener listener) {
        mFeedbackListener = listener;
        return this;
    }

    /**
     * 反馈点击选择,view状态改变的接口
     */
    public interface OnCircleMenuFeedbackListener {
        /**
         * 点击选择子菜单
         *
         * @param index
         */
        void onMenuSelected(int index);

        /**
         * 点击中心图标
         *
         */
        void onMenuCenterClicked();

        /**
         * 菜单打开
         */
        void onMenuOpened();

        /**
         * 菜单关闭
         */
        void onMenuClosed();
    }

    /**
     * 获取当前menu的状态
     *
     * @return
     */
    public int getCurrentMenuState() {
        return state;
    }

    /**
     * 是否拦截点击手机返回键
     */
    public CircleMenuView setInterceptBackPressedEnable(boolean isEnable) {
        if (isEnable) {
            requestFocus();
            setFocusable(true);
            setFocusableInTouchMode(true);
        }
        return this;
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK && isOpened()) {
            // 点击返回键的时候,如果菜单是打开状态的话,关闭
            closeMenu();
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }


}

xml代码:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:background="@color/colorPrimary"
    tools:context=".MainActivity">


    <com.chy.mytestmap.CircleMenuView
        android:id="@+id/circle_menu"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/colorAccent">
    </com.chy.mytestmap.CircleMenuView>


</RelativeLayout>

使用方法:

 private void initControls(){
        circleMenu = findViewById(R.id.circle_menu);
        circleMenu.setMainMenu(Color.parseColor("#ffffff"), R.mipmap.ic_launcher, R.mipmap.ic_launcher)
                .addSubMenu(Color.parseColor("#ffffff"), R.mipmap.ic_launcher)
                .addSubMenu(Color.parseColor("#ffffff"), R.mipmap.ic_launcher)
                .addSubMenu(Color.parseColor("#ffffff"), R.mipmap.ic_launcher)
                .addSubMenu(Color.parseColor("#ffffff"), R.mipmap.ic_launcher)

                // 这里开启返回键拦截,主要就是按下返回键关闭菜单
                .setInterceptBackPressedEnable(true)
                .setFeedbackListener(new CircleMenuView.OnCircleMenuFeedbackListener() {
                    @Override
                    public void onMenuSelected(int index) {
                        Toast.makeText(getApplicationContext(), "点击了 [" + index + "]", Toast.LENGTH_SHORT).show();
                    }

                    @Override
                    public void onMenuCenterClicked() {
                        Toast.makeText(getApplicationContext(), "点击了 [中心]", Toast.LENGTH_SHORT).show();
                    }

                    @Override
                    public void onMenuOpened() {

                    }

                    @Override
                    public void onMenuClosed() {

                    }
                });
    }

猜你喜欢

转载自blog.csdn.net/qq_19688207/article/details/130592885