仿遥控器菜单圆盘

仿遥控器菜单圆盘

1.效果

这里写图片描述


2.github地址

github地址
https://github.com/caixingcun/CopyMenu

3.前言

之前有一家面试,有这么一个需求,问我需要如何实现这么一个效果
刚开始感觉相对布局什么也能实现差不多的,但面试官提问到如何处理重叠部分
之后回答说自定义控件,因为自定义控件写的少,也就只有个大概思路
一带而过了,回来花了点时间,搞出来,跟大家分享下,也是来扩充博客

4.原理

这样的需要主要麻烦的地方是在点击后,响应特定区块
如果只是单纯画图 和 点击对应区域回调,完全可以画同心圆 和 线来实现
这里是将每一个区块的线上的点计算出来 连成path
创建特定标志位记录点击区域,重绘区块实现区块变色

5.代码

step1.创建一个类继承View,创建构造函数,在构造中初始化自定义属性 和 画笔

public class MenuView extends View 
 public MenuView(Context context) {
        this(context, null);
    }

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

    public MenuView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //获取自定义属性。
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.menuview);
        //获取字体大小,默认大小是16dp
        mLineColor = ta.getColor(R.styleable.menuview_lineColor, Color.BLACK);
        mArrowColor = ta.getColor(R.styleable.menuview_arrowColor, Color.BLACK);
        mChoseColor = ta.getColor(R.styleable.menuview_choseColor, Color.BLACK);
        mArrowChoseColor = ta.getColor(R.styleable.menuview_arrowChoseColor, Color.WHITE);
        mText = (String) ta.getText(R.styleable.menuview_mtext);
        mTextSize = ta.getDimension(R.styleable.menuview_mtextSize, 30);
        Log.d("tag", "mTextSize" + mTextSize);
        ta.recycle();

        mPaint = new Paint();
        mPaint.setColor(mLineColor);
        mPaint.setAntiAlias(true);
        mPaint.setStrokeWidth(2);//线宽
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setTextSize(mTextSize);
        mPaint.setTypeface(Typeface.DEFAULT_BOLD);
    }

step2.自定义属性 ,声明命名空间

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="menuview">
        <!--线条颜色-->
        <attr name="lineColor" format="color"/>
        <!--箭头颜色-->
        <attr name="arrowColor" format="color"/>
        <!--选择反色-->
        <attr name="choseColor" format="color"/>
        <!--选择箭头反色-->
        <attr name="arrowChoseColor" format="color"/>
        <!--中间OK或者菜单字体大小-->
        <attr name="mtextSize" format="dimension"/>
        <!--中间文字内容-->
        <attr name="mtext" format="string"/>

    </declare-styleable>
</resources>

step3.重写onMeasure方法回去控件宽高

   @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        Log.d("tag", "onMeasure");
        //1920x1080分辨率
        mWidth = getMeasuredWidth();  //获取控件宽度和高度
        mHeight = getMeasuredHeight();//600,600

step4.重写onDraw方法进行绘制界面

 private PointF mCenterPoint = new PointF(200f, 200f);
    private float mR = mWidth * 0.5f;
    private float mr = mR * 75f / 200f;
    private float gen2 = 1.41421356f;
    private PointF mPointA = new PointF(mCenterPoint.x - mR / gen2, mCenterPoint.y - mR / gen2);
    private PointF mPointB = new PointF(mCenterPoint.x + mR / gen2, mCenterPoint.y - mR / gen2);
    private PointF mPointC = new PointF(mCenterPoint.x + mr / gen2, mCenterPoint.y - mr / gen2);
    private PointF mPointD = new PointF(mCenterPoint.x - mr / gen2, mCenterPoint.y - mr / gen2);

    private PointF mPointE = new PointF(mCenterPoint.x - mR / gen2, mCenterPoint.y + mR / gen2);
    private PointF mPointF = new PointF(mCenterPoint.x + mR / gen2, mCenterPoint.y + mR / gen2);
    private PointF mPointG = new PointF(mCenterPoint.x + mr / gen2, mCenterPoint.y + mr / gen2);
    private PointF mPointH = new PointF(mCenterPoint.x - mr / gen2, mCenterPoint.y + mr / gen2);

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Log.d("tag", "onDraw");
       /** 圆心*/
        mCenterPoint = new PointF(mWidth * 0.5f,
                mHeight * 0.5f);
        Log.d("tag", mCenterPoint.x + "mCenterPoint.x" + mCenterPoint.y + "mCenterPoint.y");
        /**大小半径*/
        mR = mWidth * 0.5f;
        mr = mR * 75f / 200f;
        /** 四个圆弧区域的路径相关点*/
        mPointA = new PointF(mCenterPoint.x - mR / gen2, mCenterPoint.y - mR / gen2);
        mPointB = new PointF(mCenterPoint.x + mR / gen2, mCenterPoint.y - mR / gen2);
        mPointC = new PointF(mCenterPoint.x + mr / gen2, mCenterPoint.y - mr / gen2);
        mPointD = new PointF(mCenterPoint.x - mr / gen2, mCenterPoint.y - mr / gen2);

        mPointE = new PointF(mCenterPoint.x - mR / gen2, mCenterPoint.y + mR / gen2);
        mPointF = new PointF(mCenterPoint.x + mR / gen2, mCenterPoint.y + mR / gen2);
        mPointG = new PointF(mCenterPoint.x + mr / gen2, mCenterPoint.y + mr / gen2);
        mPointH = new PointF(mCenterPoint.x - mr / gen2, mCenterPoint.y + mr / gen2);
        Log.d("tag", mPointA.x + "mPointA.X" + mPointA.y + "mPointA.y");
        Log.d("tag", mPointB.x + "mPointB.X" + mPointB.y + "mPointB.y");

        /**画圆弧块*/
        //左侧
        drawSrcBlock(mChoseBlock == Block.TOP, mPointA, -135, mPointC, -45, canvas);
        //顶部
        drawSrcBlock(mChoseBlock == Block.RIGHT, mPointB, -45, mPointG, 45, canvas);
        //右侧
        drawSrcBlock(mChoseBlock == BOTTOM, mPointF, 45, mPointH, 135, canvas);
        //下册
        drawSrcBlock(mChoseBlock == LEFT, mPointE, 135, mPointD, 225, canvas);

       /**画中心圆,方便上色*/
        drawCircle(mChoseBlock == Block.CENTER, mCenterPoint, mr, canvas);

        //三角形高
        mTriHeight = 1.0f / 3.0f * (mR - mr);
        //三角形顶端距离圆心距离
        disForCenter = mr + 2f * mTriHeight;

        /**画四个箭头*/

        PointF top1 = new PointF(mCenterPoint.x, mCenterPoint.y - disForCenter);
        PointF top2 = new PointF(mCenterPoint.x + mTriHeight,
                mCenterPoint.y - disForCenter + mTriHeight);
        PointF top3 = new PointF(mCenterPoint.x - mTriHeight,
                mCenterPoint.y - disForCenter + mTriHeight);
        drawTriangle(mChoseBlock == Block.TOP, top1, top2, top3, canvas);

        PointF left1 = new PointF(mCenterPoint.x - disForCenter, mCenterPoint.y);
        PointF left2 = new PointF(mCenterPoint.x - disForCenter + mTriHeight,
                mCenterPoint.y + mTriHeight);
        PointF left3 = new PointF(mCenterPoint.x - disForCenter + mTriHeight,
                mCenterPoint.y - mTriHeight);
        drawTriangle(mChoseBlock == LEFT, left1, left2, left3, canvas);

        PointF bottom1 = new PointF(mCenterPoint.x, mCenterPoint.y + disForCenter);
        PointF bottom2 = new PointF(mCenterPoint.x + mTriHeight, mCenterPoint.y+disForCenter - mTriHeight);
        PointF bottom3 = new PointF(mCenterPoint.x - mTriHeight,
                mCenterPoint.y + disForCenter - mTriHeight);
        drawTriangle(mChoseBlock ==  BOTTOM, bottom1, bottom2, bottom3, canvas);

        PointF right1 = new PointF(mCenterPoint.x + disForCenter,
                mCenterPoint.y);
        PointF right2 = new PointF(mCenterPoint.x + disForCenter - mTriHeight,
                mCenterPoint.y+mTriHeight);
        PointF right3 = new PointF(mCenterPoint.x + disForCenter - mTriHeight,
                mCenterPoint.y -mTriHeight);
        drawTriangle(mChoseBlock == Block.RIGHT, right1, right2, right3, canvas);

/*        canvas.drawCircle(mCenterPoint.x, mCenterPoint.y, 200, mPaint);
        canvas.drawCircle(mCenterPoint.x, mCenterPoint.y, 75, mPaint);
        canvas.drawLine(mPointA.x, mPointA.y, mPointD.x, mPointD.y, mPaint);
        canvas.drawLine(mPointB.x, mPointB.y, mPointC.x, mPointC.y, mPaint);
        canvas.drawLine(mPointE.x, mPointE.y, mPointH.x, mPointH.y, mPaint);
        canvas.drawLine(mPointF.x, mPointF.y, mPointG.x, mPointG.y, mPaint);*/
    }
/**画箭头*/
    private void drawTriangle(boolean isSlide, PointF p1, PointF p2, PointF p3,
            Canvas canvas) {
        mPaint.setStyle(Paint.Style.FILL);  //填充中间
        if (isSlide) {
            mPaint.setColor(mArrowChoseColor);
        } else {
            mPaint.setColor(mArrowColor);
        }
        Path path = new Path();
        path.moveTo(p1.x, p1.y);
        path.lineTo(p2.x, p2.y);
        path.lineTo(p3.x, p3.y);
        path.close();
        canvas.drawPath(path, mPaint);
    }
/** 画圆  中心小圆*/
    private void drawCircle(boolean isSlide, PointF centerPoint, float mr, Canvas canvas) {
            mPaint.setColor(mLineColor);
        if (isSlide) {
            mPaint.setStyle(Paint.Style.FILL); //填充
        } else {
            mPaint.setStyle(Paint.Style.STROKE);
        }
        canvas.drawCircle(centerPoint.x, centerPoint.y, mr, mPaint);

        Rect bounds = new Rect();
        mPaint.getTextBounds(mText, 0, mText.length(), bounds);
        int textwidth = bounds.width();
        int textHeight = bounds.height();
        if (isSlide) {
            mPaint.setColor(mArrowChoseColor);
        } else {
            mPaint.setColor(mArrowColor);
        }
        canvas.drawText(mText, mCenterPoint.x - textwidth * 0.5f, mCenterPoint.y + textHeight * 0.5f,
                mPaint);
        mPaint.setColor(mLineColor);
    }
/** 画扇形边框*/
    private void drawSrcBlock(boolean isSolid, PointF pointA, int startAngle, PointF pointC,
            int startAngle1,
            Canvas canvas) {
        if (isSolid) {
            mPaint.setStyle(Paint.Style.FILL);
            mPaint.setColor(mChoseColor);
        } else {
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setColor(mLineColor);
        }
        Path path = new Path();
        path.moveTo(pointA.x, pointA.y);
        path.arcTo(new RectF(0, 0, mWidth, mWidth), startAngle, 90);
        path.lineTo(pointC.x, pointC.y);
        path.arcTo(new RectF(mWidth * 0.5f * 125f / 200f, mHeight * 0.5f * 125f / 200f,
                mWidth * 0.5f * 275f / 200f, mWidth * 0.5f * 275f / 200f), startAngle1, -90);
        path.close();
        canvas.drawPath(path, mPaint);
    }

step5.重写onTouchEvent对触摸事件进行处理,更改对应标志位,并将触摸状态回调出去

 @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:

            float x = event.getX();
            float y = event.getY();
                evaluateTouchBlock(x, y);
                break;
            case MotionEvent.ACTION_MOVE:

                break;
            case MotionEvent.ACTION_UP:
                mChoseBlock = NULL;
                invalidate();
                break;
        }


        return true;
    }

    //计算点的触摸位置块
    private void evaluateTouchBlock(float x, float y) {

        float dis = GeometryUtil.getDistanceBetween2Points(mCenterPoint,
                new PointF(x, y));
        if (dis >= mr && dis <= mR) { //大小圆之间

            float datY = y - mCenterPoint.y;
            float datX = x - mCenterPoint.x;

            if (datX==0) {//排除斜率为0状况
                if (datY > 0) {
                    mChoseBlock = BOTTOM;
                } else {
                    mChoseBlock = Block.TOP;
                }
            } else {

                float k = datY / datX;

                if ((k >= 1 || k <= -1)) {
                    if (datY <= 0) {
                        mChoseBlock = Block.TOP; //LEFT
                    } else {
                        mChoseBlock = BOTTOM; //RIGHT
                    }
                } else {
                    if (datX <= 0) {
                        mChoseBlock = LEFT; //BOTTOM
                    } else {
                        mChoseBlock = Block.RIGHT;//TOP
                    }
                }
            }
            invalidate();
        } else if (dis < mr) {//小圆
            mChoseBlock = Block.CENTER;
            invalidate();
        } else {

        }
        if (mOnTouchBlockListener != null) {
            switch (mChoseBlock) {

                case LEFT:
                    mOnTouchBlockListener.onLeft();
                    break;
                case TOP:
                    mOnTouchBlockListener.onTop();
                    break;
                case BOTTOM:
                    mOnTouchBlockListener.onBottom();
                    break;
                case RIGHT:
                    mOnTouchBlockListener.onRight();
                    break;
                case CENTER:
                    mOnTouchBlockListener.onCenter();
                    break;
            }
        }
    }

step6.使用到的回调接口,和枚举状态类

    //点击事件回调
    private OnTouchBlockListener mOnTouchBlockListener;

    public void setOnTouchBlockListener(
            OnTouchBlockListener onTouchBlockListener) {
        mOnTouchBlockListener = onTouchBlockListener;
    }

    public interface OnTouchBlockListener {
        void onTop();

        void onBottom();

        void onLeft();

        void onRight();

        void onCenter();
    }
 enum Block {
        LEFT, TOP, RIGHT, BOTTOM, CENTER, NULL
    }

step7.工具类

package com.sy.copymenu;

import android.graphics.PointF;

/**
 * 几何图形工具
 */
public class GeometryUtil {

    /**
     * As meaning of method name.
     * 获得两点之间的距离
     * @param p0
     * @param p1
     * @return
     */
    public static float getDistanceBetween2Points(PointF p0, PointF p1) {
        float distance = (float) Math.sqrt(Math.pow(p0.y - p1.y, 2) + Math.pow(p0.x - p1.x, 2));
        return distance;
    }

    /**
     * Get middle point between p1 and p2.
     * 获得两点连线的中点
     * @param p1
     * @param p2
     * @return
     */
    public static PointF getMiddlePoint(PointF p1, PointF p2) {
        return new PointF((p1.x + p2.x) / 2.0f, (p1.y + p2.y) / 2.0f);
    }

    /**
     * Get point between p1 and p2 by percent.
     * 根据百分比获取两点之间的某个点坐标
     * @param p1
     * @param p2
     * @param percent
     * @return
     */
    public static PointF getPointByPercent(PointF p1, PointF p2, float percent) {
        return new PointF(evaluateValue(percent, p1.x , p2.x), evaluateValue(percent, p1.y , p2.y));
    }

    /**
     * 根据分度值,计算从start到end中,fraction位置的值。fraction范围为0 -> 1
     * @param fraction
     * @param start
     * @param end
     * @return
     */
    public static float evaluateValue(float fraction, Number start, Number end){
        return start.floatValue() + (end.floatValue() - start.floatValue()) * fraction;
    }


    /**
     * Get the point of intersection between circle and line.
     * 获取 通过指定圆心,斜率为lineK的直线与圆的交点。
     * 
     * @param pMiddle The circle center point.
     * @param radius The circle radius.
     * @param lineK The slope of line which cross the pMiddle.
     * @return
     */
    public static PointF[] getIntersectionPoints(PointF pMiddle, float radius, Double lineK) {
        PointF[] points = new PointF[2];

        float radian, xOffset = 0, yOffset = 0; 
        if(lineK != null){
            radian= (float) Math.atan(lineK);
            xOffset = (float) (Math.sin(radian) * radius);
            yOffset = (float) (Math.cos(radian) * radius);
        }else {
            xOffset = radius;
            yOffset = 0;
        }
        points[0] = new PointF(pMiddle.x + xOffset, pMiddle.y - yOffset);
        points[1] = new PointF(pMiddle.x - xOffset, pMiddle.y + yOffset);

        return points;
    }
}

step8. xml

  <com.sy.copymenu.MenuView
        android:id="@+id/menu_view"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_centerInParent="true"
        android:background="@android:color/transparent"

        menuview:arrowChoseColor="@android:color/white"
        menuview:arrowColor="@android:color/holo_red_dark"
        menuview:choseColor="@android:color/black"
        menuview:lineColor="@android:color/black"
        menuview:mtext="OK"
        menuview:mtextSize="20sp"
    />

step9.状态回调监听

  mMenuView = (MenuView) findViewById(R.id.menu_view);
        mMenuView.setOnTouchBlockListener(new MenuView.OnTouchBlockListener() {
            @Override
            public void onTop() {
                ToastUtils.showToast(mContext, "top");
            }

            @Override
            public void onBottom() {
                ToastUtils.showToast(mContext, "onBottom");
            }

            @Override
            public void onLeft() {
                ToastUtils.showToast(mContext, "onLeft");
            }

            @Override
            public void onRight() {
                ToastUtils.showToast(mContext, "onRight");
            }

            @Override
            public void onCenter() {
                ToastUtils.showToast(mContext, "onCenter");
            }
        });

猜你喜欢

转载自blog.csdn.net/jiushiwo12340/article/details/75807964