android开发非-转动圆形菜单

项目中遇到过 圆形菜单布局的 要求实现效果转动,效果图片如下
圆形菜单
这个是写的一个demo展示,拿来微调就可以用,这个较为原始,线上的项目再用(优化了绘制等一系列) 但是你可以对该自定义view进行优化以符合你的需求,布局和activity引用我就不贴了,看了view就知道了,最后上代码:

/**
 * Created by Administrator on 4/20 0020.
 */
public class CakeView extends View {

    private Context ctx;
    private DecimalFormat format;
    private List<BaseMessage> mList;

    private Paint arcPaint;
    private Paint linePaint;
    private Paint textPaint;

    private float centerX;
    private float centerY;
    private float radius;
    private float total;
    private float startAngle;
    private float textAngle;
    private float roundAngle = 360f;
    private boolean isAddText = true;
    private float mFontSize;
    private Bitmap mCenterBitMap;
    private int mCenterWidth = 110;

    private List<PointF[]> lineList;
    private List<PointF> textList;
    private Region[] regionList;
    private RectF centerRect;
    private Canvas mCanvas;

    private MyHandler handler;
    private ViewOnclickListener mViewOnclickListener;

    private int refeshNum = 0;
    private boolean isRefesh = false;

    /**
     * 检测按下到抬起时旋转的角度
     */
    private float mTmpAngle;
    /**
     * 检测按下到抬起时使用的时间
     */
    private long mDownTime;

    /**
     * 判断是否正在自动滚动
     */
    private boolean isFling;

    /**
     * 整个View的旋转角度
     */
    private float rotationAngle;

    private float start = 0;
    /**
     * 获得当前的角度
     */
    private float end = 0;

    /**
     * 每秒最大移动角度
     */
    private int mMax_Speed;
    /**
     * 如果移动角度达到该值,则屏蔽点击
     */
    private int mMin_Speed;

    /**
     * 判断是否正在自动滚动
     */
    private boolean isQuickMove;

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

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

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

    private void init(Context context) {
        this.ctx = context;
        this.handler = new MyHandler(this);
        this.lineList = new ArrayList<>();
        this.textList = new ArrayList<>();
        this.mList = new ArrayList<>();
        this.format = new DecimalFormat("##0.00");

        this.arcPaint = new Paint();
        this.arcPaint.setAntiAlias(true);
        this.arcPaint.setDither(true);
        this.arcPaint.setStyle(Paint.Style.STROKE);
//        this.arcPaint.setColor(Color.parseColor("#FFFFFF"));

        this.linePaint = new Paint();
        this.linePaint.setAntiAlias(true);
        this.linePaint.setDither(true);
        this.linePaint.setStyle(Paint.Style.STROKE);
        this.linePaint.setStrokeWidth(dip2px(ctx, 20));
        this.linePaint.setColor(Color.parseColor("#FFFFFF"));

        this.textPaint = new Paint();
        this.textPaint.setAntiAlias(true);
        this.textPaint.setDither(true);
        this.textPaint.setTextSize(40);
        this.textPaint.setStyle(Paint.Style.FILL);
        this.textPaint.setColor(Color.parseColor("#FFFFFF"));
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width;
        int height;
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

        if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.EXACTLY) {
            height = heightSpecSize;
            width = Math.min(heightSpecSize, Math.min(getScreenSize(ctx)[0], getScreenSize(ctx)[1]));
        } else if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.AT_MOST) {
            width = widthSpecSize;
            height = Math.min(widthSpecSize, Math.min(getScreenSize(ctx)[0], getScreenSize(ctx)[1]));
        } else if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
            width = height = Math.min(getScreenSize(ctx)[0], getScreenSize(ctx)[1]);
        } else {
            width = widthSpecSize;
            height = heightSpecSize;
        }

        Log.e("宽高", width + "," + height);
        setMeasuredDimension(width + 20, height+20);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        centerX = w / 2;
        centerY = h / 2;
        radius = Math.min(centerX, centerY) * 0.725f;//0.725f
        arcPaint.setStrokeWidth(radius / 3 * 2);
        textPaint.setTextSize(radius / 10);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        refeshNum++;
        Log.e("onDraw--->>", "重绘次数=" + refeshNum);
        textList.clear();
        lineList.clear();
        lineList = new ArrayList<>();
        textList = new ArrayList<>();
        mCanvas = null;
        mCanvas = canvas;
        if (mList != null) {

            RectF mRectF = new RectF(centerX - radius, centerY - radius, centerX + radius, centerY + radius);
            for (int i = 0; i < mList.size(); i++) {
                arcPaint.setColor(mList.get(i).color);
                canvas.drawArc(mRectF, startAngle, mList.get(i).percent / total * roundAngle, false, arcPaint);

                if (mCenterBitMap != null) {
                    centerRect = new RectF(centerX - mCenterWidth / 2, centerY - mCenterWidth / 2, centerX + mCenterWidth / 2, centerY + mCenterWidth / 2);
                    canvas.drawBitmap(mCenterBitMap, null, centerRect, null);
                }
                if (isAddText) {
                    lineList.add(getLinePointFs(startAngle));//获取直线 开始坐标 结束坐标
                    textAngle = startAngle + mList.get(i).percent / total * roundAngle / 2;
                    textList.add(getTextPointF(textAngle));   //获取文本文本
                }

                startAngle += mList.get(i).percent / total * roundAngle;
            }

//            drawSpacingLine(canvas, lineList);
            // 绘制文字
//            drawText(canvas);

        }

        if (roundAngle < 360f) {
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    roundAngle += 10f;
                    if (roundAngle == 360f - 10f) {
                        isAddText = true;
                    }
//                    postInvalidate();
                }
            }, 0);
//            postInvalidate();
        } else {
            // 绘制间隔空白线
            drawSpacingLine(canvas, lineList);
            // 绘制文字
            drawText(canvas);

//            if (handler != null) {
//                handler.removeCallbacksAndMessages(null);
//            }
        }
//         绘制间隔空白线
//        Log.e("cs", "" + roundAngle);
        if (!isRefesh || isFling) {
            postInvalidate();
        }
        isRefesh = !isRefesh;
    }

    /**
     * 获取文本文本
     *
     * @return
     */
    private PointF getTextPointF(float angle) {
        float textPointX = (float) (centerX + radius * Math.cos(Math.toRadians(angle)));
        float textPointY = (float) (centerY + radius * Math.sin(Math.toRadians(angle)));
        return new PointF(textPointX, textPointY);
    }

    /**
     * 获取直线 开始坐标 结束坐标
     */
    private PointF[] getLinePointFs(float angle) {
        float stopX = (float) (centerX + (radius + arcPaint.getStrokeWidth()) * Math.cos(Math.toRadians(angle)));
        float stopY = (float) (centerY + (radius + arcPaint.getStrokeWidth()) * Math.sin(Math.toRadians(angle)));
        float startX = (float) (centerX + (radius - arcPaint.getStrokeWidth()) * Math.cos(Math.toRadians(angle)));
        float startY = (float) (centerY + (radius - arcPaint.getStrokeWidth()) * Math.sin(Math.toRadians(angle)));
        PointF startPoint = new PointF(startX, startY);
        PointF stopPoint = new PointF(stopX, stopY);
        return new PointF[]{startPoint, stopPoint};
    }

    /**
     * 画间隔线
     *
     * @param canvas
     */
    private void drawSpacingLine(Canvas canvas, List<PointF[]> pointFs) {
        for (int i = 0; i < pointFs.size(); i++) {
            PointF[] fp = pointFs.get(i);
            canvas.drawLine(fp[0].x, fp[0].y, fp[1].x, fp[1].y, linePaint);
        }
    }

    /**
     * 画文本
     *
     * @param canvas
     */
    private void drawText(Canvas canvas) {
        regionList = new Region[textList.size()];
        for (int i = 0; i < textList.size(); i++) {
            textPaint.setTextAlign(Paint.Align.CENTER);
            String text = mList.get(i).content;
            Bitmap bitmap = mList.get(i).image;
            canvas.drawText(text, textList.get(i).x, textList.get(i).y + dip2px(getContext(), 25), textPaint);
//            Paint.FontMetrics fm = textPaint.getFontMetrics();
//            canvas.drawText(format.format(mList.get(i).percent * 100 / total) + "%", textList.get(i).x, textList.get(i).y + (fm.descent - fm.ascent), textPaint);
            // 设置绘制图片的区域
            // 设为默认位置
            // todo 计算默认方位有错,自行修改
            Rect rect = new Rect((int) (textList.get(i).x - (bitmap.getWidth() / 2)),
                    (int) (textList.get(i).y) - (bitmap.getHeight() * 5 / 4 - dip2px(getContext(), 20)),
                    (int) textList.get(i).x + (bitmap.getWidth() / 2),
                    (int) textList.get(i).y - (bitmap.getHeight() / 4) + dip2px(getContext(), 20));

            Region re = new Region();
            Path path = new Path();
            path.moveTo((float) (textList.get(i).x - dip2px(getContext(), 35)), (float) textList.get(i).y - dip2px(getContext(), 35));
            path.lineTo((float) (textList.get(i).x + dip2px(getContext(), 35)), (float) textList.get(i).y - dip2px(getContext(), 35));
            path.lineTo((float) (textList.get(i).x + dip2px(getContext(), 35)), (float) textList.get(i).y + dip2px(getContext(), 35));
            path.lineTo((float) (textList.get(i).x - dip2px(getContext(), 35)), (float) textList.get(i).y + dip2px(getContext(), 35));
            path.close();

            RectF r = new RectF();
            //计算控制点的边界
            path.computeBounds(r, true);
            //设置区域路径和剪辑描述的区域
            re.setPath(path, new Region((int) r.left, (int) r.top, (int) r.right, (int) r.bottom));
            regionList[i] = re;
            canvas.drawBitmap(bitmap, null, rect, null);
        }
    }


    /**
     * 设置间隔线的颜色
     *
     * @param color
     */
    public void setSpacingLineColor(int color) {
        linePaint.setColor(color);
    }


    /**
     * 设置间隔线的宽度
     *
     * @param width
     */
    public void setSpacingLineWidth(float width) {
        linePaint.setStrokeWidth(width);
    }

    /**
     * 设置文本颜色
     *
     * @param color
     */
    public void setTextColor(int color) {
        textPaint.setColor(color);
    }

    /**
     * 设置中心颜色
     *
     * @param color
     */
    public void setCenterColor(int color) {
        arcPaint.setColor(color);
    }

    /**
     * 设置开始角度
     *
     * @param startAngle
     */
    public void setStartAngle(float startAngle) {
        this.startAngle = startAngle;
    }


    /**
     * 设置标签文字大小
     *
     * @param mFontSize
     */
    public void setFontSize(float mFontSize) {
        this.mFontSize = mFontSize;
    }

    /**
     * 设置中心图标
     *
     * @param mCenterBitMap
     */
    public void setCenterBitMap(Bitmap mCenterBitMap) {
        this.mCenterBitMap = mCenterBitMap;
    }

    /**
     * 设置中心图标宽度
     *
     * @param mCenterWidth
     */
    public void setCenterWidth(int mCenterWidth) {
        this.mCenterWidth = mCenterWidth;
    }

    /**
     * 设置饼的宽度
     *
     * @param width
     */
    public void setCakeStrokeWidth(int width) {
        arcPaint.setStrokeWidth(dip2px(ctx, width));
    }

    /**
     * 设置饼的数据
     *
     * @param mList
     */
    public void setCakeData(List<BaseMessage> mList) {
        total = 0;
        if (mList == null) {
            return;
        }
        for (int i = 0; i < mList.size(); i++) {
            total += mList.get(i).percent;
        }
        this.mList.clear();
        this.mList = mList;
    }

    /**
     * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
     */
    public static int dip2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    /**
     * 根据手机的分辨率从 px(像素) 的单位 转成为 dp
     */
    public static int px2dip(Context context, float pxValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5f);
    }

    public static int[] getScreenSize(Context context) {
        WindowManager wm = (WindowManager) context.getSystemService(
                Context.WINDOW_SERVICE);
        DisplayMetrics outMetrics = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(outMetrics);
        return new int[]{outMetrics.widthPixels, outMetrics.heightPixels};
    }

    private static class MyHandler extends Handler {
        private WeakReference<CakeView> activityWeakReference;

        public MyHandler(CakeView activity) {
            activityWeakReference = new WeakReference<CakeView>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            CakeView activity = activityWeakReference.get();
            if (activity == null) {
                return;
            }
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return true;
    }


    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        //不希望父控件拦截事件
        getParent().requestDisallowInterceptTouchEvent(true);
        float x = event.getX();
        float y = event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mLastX = x;
                mLastY = y;
                mDownTime = System.currentTimeMillis();
                mTmpAngle = 0;

                // 如果当前已经在快速滚动
                if (isQuickMove) {
                    // 移除快速滚动的回调
                    removeCallbacks(mFlingRunnable);
                    isQuickMove = false;
                    return true;
                }
                isMove = true;

                break;
            case MotionEvent.ACTION_MOVE:
//                LogUtil.e(TAG, "ACTION_MOVE");
                isMove = false;

                start = (float) getAngle(mLastX, mLastY);
                end = (float) getAngle(x, y);
//                LogUtil.e(TAG, start + " =start " + end + " ,  =end");

                //  二、三象限,色角度值是付值
                if (getQuadrant(x, y) == 3 || getQuadrant(x, y) == 2) {
                    rotationAngle += start - end;
                    mTmpAngle += start - end;
                } else {
                    //如果是一、四象限,则直接end-start,角度值都是正值
                    rotationAngle += end - start;
                    mTmpAngle += end - start;
                }

                // 重新布局
//                postInvalidate();
                getCheck();
                break;
            case MotionEvent.ACTION_UP:
                // 获取每秒移动的角度
                float anglePerSecond = mTmpAngle * 1000
                        / (System.currentTimeMillis() - mDownTime);
                // 如果达到最大速度
                if (Math.abs(anglePerSecond) > mMax_Speed && !isQuickMove) {
                    // 惯性滚动
                    if (Math.abs(start-end)>=1){ //放手时大于1度才惯性滚动
                        post(mFlingRunnable = new AngleRunnable(anglePerSecond));
                    }
                    return true;
                }

                // 如果当前旋转角度超过minSpeed屏蔽点击
                if (Math.abs(mTmpAngle) > mMin_Speed) {
                    return true;
                }

                if (!isMove) {
                    for (int i = 0; i < regionList.length; i++) {
                        Region rect = regionList[i];
                        if (rect.contains((int) x, (int) y)) {
                            if (mViewOnclickListener != null) {
                                mViewOnclickListener.onViewClick(this, i);
                            }
                        }
                    }

                    if (centerRect != null) {
                        if (centerRect.contains((int) x, (int) y)) {
                            if (mViewOnclickListener != null) {
                                mViewOnclickListener.onViewCenterClick();
                            }
                        }
                    }
                }
                break;
        }

        return true;
    }

    private void getCheck() {
        rotationAngle %= 360;
        setRotation(rotationAngle);
    }

    private double distanceTwoPointF(PointF A, PointF B){
        double disX= Math.abs(A.x)- Math.abs(B.x);
        double disY= Math.abs(A.y)- Math.abs(B.y);
        return Math.sqrt((disX)*(disX)+(disY)*(disY));
    }

    public interface onYuanPanClickListener {
        void onClick(View v, int position);
    }

    public interface ViewOnclickListener {
        void onViewClick(View v, int position);

        void onViewCenterClick();
    }

    public ViewOnclickListener getViewOnclickListener() {
        return mViewOnclickListener;
    }

    public void setViewOnclickListener(ViewOnclickListener viewOnclickListener) {
        mViewOnclickListener = viewOnclickListener;
    }

    /**
     * 记录上一次的x,y坐标
     */
    private float mLastX;
    private float mLastY;


    /**
     * 移动转盘
     */
    private boolean isMove = false;

    /**
     * 自动滚动的Runnable
     */
    private AngleRunnable mFlingRunnable;

    /**
     * 当每秒移动角度达到该值时,认为是快速移动
     */
    private static final int FLINGABLE_VALUE = 300;

    /**
     * 如果移动角度达到该值,则屏蔽点击
     */
    private static final int NOCLICK_VALUE = 3;

    /**
     * 当每秒移动角度达到该值时,认为是快速移动
     */
    private int mFlingableValue = FLINGABLE_VALUE;


    private static final String TAG = "CakeView";


    /**
     * 根据触摸的位置,计算角度
     *
     * @param xTouch
     * @param yTouch
     * @return
     */
    private float getAngle(float xTouch, float yTouch) {
        double x = xTouch - (getWidth() / 2d);
        double y = yTouch - (getHeight() / 2d);
        return (float) (Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI);
    }

    /**
     * 根据当前位置计算象限
     *
     * @param x
     * @param y
     * @return
     */
    private int getQuadrant(float x, float y) {
        int tmpX = (int) (x - getWidth() / 2);
        int tmpY = (int) (y - getHeight() / 2);
        if (tmpX >= 0) {
            return tmpY >= 0 ? 4 : 1;
        } else {
            return tmpY >= 0 ? 3 : 2;
        }
    }

    /**
     * 惯性滚动
     */
    private class AngleRunnable implements Runnable {

        private float angelPerSecond;

        public AngleRunnable(float velocity) {
            this.angelPerSecond = velocity;
        }

        public void run() {
            //小于20停止
            if ((int) Math.abs(angelPerSecond) < 20) {
                isQuickMove = false;
                return;
            }
            isQuickMove = true;
            // 滚动时候不断修改滚动角度大小
            rotationAngle += (angelPerSecond / 30);
            //逐渐减小这个值
            angelPerSecond /= 1.0666F;
            postDelayed(this, 30);
            // 重新布局
            getCheck();
        }
    }


    /**
     * 自动滚动的任务
     *
     * @author zhy
     */
    private class AutoFlingRunnable implements Runnable {

        private float angelPerSecond;
        private int flg;

        public AutoFlingRunnable(float velocity,float flg) {
            this.angelPerSecond = velocity;
            this.flg=(int) flg;
        }

        public void run() {
            // 如果小于20,则停止
            if ((int) Math.abs(angelPerSecond) < 20) {
                isFling = false;
                return;
            }
            isFling = true;
            // 不断改变mStartAngle,让其滚动,/30为了避免滚动太快
            startAngle += (flg*angelPerSecond / 10);
            // 逐渐减小这个值
            angelPerSecond /= 1.0666F;
            // 绘制间隔空白线
            drawSpacingLine(mCanvas, lineList);
            postDelayed(this, 30);
            // 重新布局
            requestLayout();
        }
    }

}

在此借鉴了一些大神的自定义转盘实现,记录一下为了类似以前的建行圆形菜单效果+扇形色块背景 手指滑动菜单旋转很容易出现问题!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值