上下左右-方向控制自定义View

该博客介绍了一个自定义的Android视图组件,用于实现类似汽车方向盘的UI控制。代码详细展示了如何绘制方向盘的各个部分,包括大圆、箭头、颜色渐变等,并通过监听触摸事件来响应不同方向的转动。此外,还提供了一个接口`WheelTouch`来处理触摸事件回调。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.实例图片

2.实例代码


/**
 * 方向盘 Steering wheel
 * Created by lsw.
 */
public class SteeringWheelView extends View {

    // 箭头长度
    private final static int directionWidth = 25;

    // 箭头宽度
    private final static float DIRECTION_WIDTH = 6.0f;

    // X偏移值
    private final static int deviationX = 2;
    // Y偏移值
    private final static int deviationY = 2;

    // 大圆宽度
    private float CIRCLE_WIDTH;

    // 大圆直径
    private float WIDTH_HEIGHT;

    // 大圆画笔
    private Paint paintCircle;

    // 箭头画笔
    private Paint paintDirectionUpper;
    private Paint paintDirectionLower;
    private Paint paintDirectionLeft;
    private Paint paintDirectionRight;
    private Paint paintwhite;

    // 外圈画笔
    private Paint paintMiddleCircle;

    // 内图标画笔
    private Paint paintMiddleCircleBg;

    // 内图标画图区域
    private RectF oval;
    // 外大圆指向区域
    private RectF ovalDeviation;

    // 箭头路径
    private Path path1;
    private Path path2;
    private Path path3;
    private Path path4;
    private Path path5;

    // 各控件使用状态
    private boolean upperTurn = false;
    private boolean lowerTurn = false;
    private boolean leftTurn = false;
    private boolean rightTurn = false;
    private boolean MTurn = false;

    private WheelTouch wheelTouch;
    private LinearGradient linearGradient;
    private float cha;
    private int white;
    private Path path6;

    public SteeringWheelView(Context context) {
        super(context);
        initData(context);
    }

    public SteeringWheelView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initData(context);
    }

    public SteeringWheelView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initData(context);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public SteeringWheelView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        initData(context);
    }

    public void setWheelTouch(WheelTouch wheelTouch) {
        this.wheelTouch = wheelTouch;
    }

    /**
     * convert dp to its equivalent px
     * <p>
     * 将dp转换为与之相等的px
     */
    public static float dp2px(Context context, float dipValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (dipValue * scale + 0.5f);
    }

    /**
     * 初始化数据
     * @param context 上下文
     */
    private void initData(Context context) {
        white = Color.parseColor("#FFFFFF");
        cha = dp2px(getContext(), 4);
        WIDTH_HEIGHT = dp2px(context, 180);

        CIRCLE_WIDTH = 6.0f;

        paintCircle = new Paint();

        paintCircle.setAntiAlias(true);
        paintCircle.setDither(true);
        paintCircle.setStyle(Paint.Style.FILL);
        paintCircle.setStrokeWidth(CIRCLE_WIDTH);
//        paintCircle.setColor(Color.parseColor("#C3BFBE"));
        paintCircle.setColor(Color.parseColor("#E1E1E1"));


        // 上
        paintDirectionUpper = new Paint();
        paintDirectionUpper.setAntiAlias(true);
        paintDirectionUpper.setDither(true);
        paintDirectionUpper.setStyle(Paint.Style.FILL);
        paintDirectionUpper.setStrokeWidth(DIRECTION_WIDTH);
        paintDirectionUpper.setStrokeCap(Paint.Cap.ROUND);
        paintDirectionUpper.setColor(Color.parseColor("#97694F"));

        // 下
        paintDirectionLower = new Paint();
        paintDirectionLower.setAntiAlias(true);
        paintDirectionLower.setDither(true);
        paintDirectionLower.setStyle(Paint.Style.FILL);
        paintDirectionLower.setStrokeWidth(DIRECTION_WIDTH);
        paintDirectionLower.setStrokeCap(Paint.Cap.ROUND);
        paintDirectionLower.setColor(Color.parseColor("#97694F"));

        // 左
        paintDirectionLeft = new Paint();
        paintDirectionLeft.setAntiAlias(true);
        paintDirectionLeft.setDither(true);
        paintDirectionLeft.setStyle(Paint.Style.FILL);
        paintDirectionLeft.setStrokeWidth(DIRECTION_WIDTH);
        paintDirectionLeft.setStrokeCap(Paint.Cap.ROUND);
        paintDirectionLeft.setColor(Color.parseColor("#97694F"));

        // 右
        paintDirectionRight = new Paint();
        paintDirectionRight.setAntiAlias(true);
        paintDirectionRight.setDither(true);
        paintDirectionRight.setStyle(Paint.Style.FILL);
        paintDirectionRight.setStrokeWidth(DIRECTION_WIDTH);
        paintDirectionRight.setStrokeCap(Paint.Cap.ROUND);
        paintDirectionRight.setColor(Color.parseColor("#97694F"));

        //
        paintwhite = new Paint();
        paintwhite.setAntiAlias(true);
        paintwhite.setDither(true);
        paintwhite.setStyle(Paint.Style.FILL);
        paintwhite.setStrokeWidth(DIRECTION_WIDTH);
        paintwhite.setStrokeCap(Paint.Cap.ROUND);

        paintwhite.setColor(white);


        // 中间外圈
        paintMiddleCircle = new Paint();
        paintMiddleCircle.setAntiAlias(true);
        paintMiddleCircle.setDither(true);
        paintMiddleCircle.setStyle(Paint.Style.FILL);
        paintMiddleCircle.setStrokeWidth(DIRECTION_WIDTH);
        paintMiddleCircle.setStrokeCap(Paint.Cap.ROUND);
        paintMiddleCircle.setColor(white);

        // 中间内圈
        paintMiddleCircleBg = new Paint();
        paintMiddleCircleBg.setAntiAlias(true);
        paintMiddleCircleBg.setDither(true);
        paintMiddleCircleBg.setStyle(Paint.Style.STROKE);
        paintMiddleCircleBg.setStrokeWidth(DIRECTION_WIDTH);
        paintMiddleCircleBg.setStrokeCap(Paint.Cap.ROUND);
        paintMiddleCircleBg.setColor(white);

        // 内圈区域
        oval = new RectF(WIDTH_HEIGHT / 2 - WIDTH_HEIGHT / 15 + deviationX, WIDTH_HEIGHT / 2 - WIDTH_HEIGHT / 15 + 2 + deviationY,
                WIDTH_HEIGHT / 2 + WIDTH_HEIGHT / 15 + deviationX, WIDTH_HEIGHT / 2 + WIDTH_HEIGHT / 15 + 2 + deviationY);

        // 大外圈区域
        ovalDeviation = new RectF(deviationX * 2, deviationY * 2, WIDTH_HEIGHT + deviationX * 2, WIDTH_HEIGHT + deviationY * 2);

        // 箭头路径
        path1 = new Path();
        path2 = new Path();
        path3 = new Path();
        path4 = new Path();
        // 按键间隔
        path5 = new Path();
        path6 = new Path();
    }

    /**
     * 比onDraw先执行
     * <p>
     * 一个MeasureSpec封装了父布局传递给子布局的布局要求,每个MeasureSpec代表了一组宽度和高度的要求。
     * 一个MeasureSpec由大小和模式组成
     * 它有三种模式:UNSPECIFIED(未指定),父元素部队自元素施加任何束缚,子元素可以得到任意想要的大小;
     * EXACTLY(完全),父元素决定自元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小;
     * AT_MOST(至多),子元素至多达到指定大小的值。
     * <p>
     * 它常用的三个函数:
     * 1.static int getMode(int measureSpec):根据提供的测量值(格式)提取模式(上述三个模式之一)
     * 2.static int getSize(int measureSpec):根据提供的测量值(格式)提取大小值(这个大小也就是我们通常所说的大小)
     * 3.static int makeMeasureSpec(int size,int mode):根据提供的大小值和模式创建一个测量值(格式)
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        final int minimumWidth = getSuggestedMinimumWidth();
        final int minimumHeight = getSuggestedMinimumHeight();
        Log.e("YView", "---minimumWidth = " + minimumWidth + "");
        Log.e("YView", "---minimumHeight = " + minimumHeight + "");
        int width = measureWidth(minimumWidth, widthMeasureSpec);
        int height = measureHeight(minimumHeight, heightMeasureSpec);
        setMeasuredDimension(width, height);
    }

    private int measureWidth(int defaultWidth, int measureSpec) {

        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        Log.e("YViewWidth", "---speSize = " + specSize + "");


        switch (specMode) {
            case MeasureSpec.AT_MOST:
                defaultWidth = (int) (WIDTH_HEIGHT + CIRCLE_WIDTH * 2 + getPaddingLeft() + getPaddingRight());

                Log.e("YViewWidth", "---speMode = AT_MOST");
                break;
            case MeasureSpec.EXACTLY:
                Log.e("YViewWidth", "---speMode = EXACTLY");
                defaultWidth = specSize;
                break;
            case MeasureSpec.UNSPECIFIED:
                Log.e("YViewWidth", "---speMode = UNSPECIFIED");
                defaultWidth = Math.max(defaultWidth, specSize);
        }
        return defaultWidth;
    }

    private int measureHeight(int defaultHeight, int measureSpec) {

        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        Log.e("YViewHeight", "---speSize = " + specSize + "");

        switch (specMode) {
            case MeasureSpec.AT_MOST:
                defaultHeight = (int) (WIDTH_HEIGHT + CIRCLE_WIDTH * 2 + getPaddingTop() + getPaddingBottom());
                Log.e("YViewHeight", "---speMode = AT_MOST");
                break;
            case MeasureSpec.EXACTLY:
                defaultHeight = specSize;
                Log.e("YViewHeight", "---speSize = EXACTLY");
                break;
            case MeasureSpec.UNSPECIFIED:
                defaultHeight = Math.max(defaultHeight, specSize);
                Log.e("YViewHeight", "---speSize = UNSPECIFIED");

//        1.基准点是baseline
//        2.ascent:是baseline之上至字符最高处的距离
//        3.descent:是baseline之下至字符最低处的距离
//        4.leading:是上一行字符的descent到下一行的ascent之间的距离,也就是相邻行间的空白距离
//        5.top:是指的是最高字符到baseline的值,即ascent的最大值
//        6.bottom:是指最低字符到baseline的值,即descent的最大值

                break;
        }
        return defaultHeight;
    }


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

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

        canvas.drawColor(white);

        // 大圆
        int colorSweep[] = {Color.parseColor("#C3C1C1"),Color.parseColor("#E1E1E1"),Color.parseColor("#C3C1C1"),Color.parseColor("#E1E1E1")};
        float stops[] = {0.6f,1};
        float poss[] = {0.2f,0.4f,0.6f,1};
        linearGradient = new LinearGradient(0, 0,WIDTH_HEIGHT / 2,WIDTH_HEIGHT / 2, colorSweep, poss, Shader.TileMode.REPEAT);
        RadialGradient radialGradient = new RadialGradient(WIDTH_HEIGHT / 2 + CIRCLE_WIDTH, WIDTH_HEIGHT / 2 + CIRCLE_WIDTH + 2, WIDTH_HEIGHT / 2,
                colorSweep, null, Shader.TileMode.MIRROR);

//        paintCircle.setShader(radialGradient);
        canvas.drawCircle(WIDTH_HEIGHT / 2 + CIRCLE_WIDTH, WIDTH_HEIGHT / 2 + CIRCLE_WIDTH + 2, WIDTH_HEIGHT / 2, paintCircle);
        // 选择的是上
        if (upperTurn) {
            paintDirectionUpper.setColor(getResources().getColor(R.color.blue));
            paintDirectionUpper.setStyle(Paint.Style.FILL);
            canvas.drawArc(ovalDeviation, 90 * 3 - 45, 90, true, paintDirectionUpper);
//            paintDirectionUpper.setStyle(Paint.Style.STROKE);
            paintDirectionUpper.setColor(Color.parseColor("#FFFFFF"));
        } else {
            paintDirectionUpper.setColor(Color.parseColor("#97694F"));
        }

        // 上箭头
        path1.moveTo(WIDTH_HEIGHT / 2 - directionWidth + deviationX, WIDTH_HEIGHT / 8 + directionWidth + deviationY); // 路径path默认是在原点(0,0),当前移植到(100,100)
        path1.lineTo(WIDTH_HEIGHT / 2 + deviationX, WIDTH_HEIGHT / 8 + deviationY);
        path1.lineTo(WIDTH_HEIGHT / 2 + directionWidth + deviationX, WIDTH_HEIGHT / 8 + directionWidth + deviationY);
        paintDirectionUpper.setStrokeJoin(Paint.Join.BEVEL);

        canvas.drawPath(path1, paintDirectionUpper);
        // 选择的是上 --------

        if (lowerTurn) {
            paintDirectionLower.setColor(getResources().getColor(R.color.blue));
            paintDirectionLower.setStyle(Paint.Style.FILL);
            canvas.drawArc(ovalDeviation, 90 - 45, 90, true, paintDirectionLower);
//            paintDirectionLower.setStyle(Paint.Style.STROKE);
            paintDirectionLower.setColor(Color.parseColor("#FFFFFF"));
        } else {
            paintDirectionLower.setColor(Color.parseColor("#97694F"));
        }

        // 下箭头
        path2.moveTo(WIDTH_HEIGHT / 2 - directionWidth + deviationX, WIDTH_HEIGHT - WIDTH_HEIGHT / 8 - directionWidth + deviationY);
        path2.lineTo(WIDTH_HEIGHT / 2 + deviationX, WIDTH_HEIGHT - WIDTH_HEIGHT / 8 + deviationY);
        path2.lineTo(WIDTH_HEIGHT / 2 + directionWidth + deviationX, WIDTH_HEIGHT - WIDTH_HEIGHT / 8 - directionWidth + deviationY);
        paintDirectionLower.setStrokeJoin(Paint.Join.BEVEL);

        canvas.drawPath(path2, paintDirectionLower);

        if (leftTurn) {
            paintDirectionLeft.setColor(getResources().getColor(R.color.blue));
            paintDirectionLeft.setStyle(Paint.Style.FILL);
            canvas.drawArc(ovalDeviation, 90 * 2 - 45, 90, true, paintDirectionLeft);
//            paintDirectionLeft.setStyle(Paint.Style.STROKE);
            paintDirectionLeft.setColor(Color.parseColor("#FFFFFF"));
        } else {
            paintDirectionLeft.setColor(Color.parseColor("#97694F"));
        }

        // 左箭头
        path3.moveTo(WIDTH_HEIGHT / 8 + directionWidth + deviationX, WIDTH_HEIGHT / 2 - directionWidth + deviationY);
        path3.lineTo(WIDTH_HEIGHT / 8 + deviationX, WIDTH_HEIGHT / 2 + deviationY);
        path3.lineTo(WIDTH_HEIGHT / 8 + directionWidth + deviationX, WIDTH_HEIGHT / 2 + directionWidth + deviationY);
        paintDirectionLeft.setStrokeJoin(Paint.Join.BEVEL);

        canvas.drawPath(path3, paintDirectionLeft);

        if (rightTurn) {
            paintDirectionRight.setColor(getResources().getColor(R.color.blue));
            paintDirectionRight.setStyle(Paint.Style.FILL);
            canvas.drawArc(ovalDeviation, -45, 90, true, paintDirectionRight);
//            paintDirectionRight.setStyle(Paint.Style.STROKE);
            paintDirectionRight.setColor(Color.parseColor("#FFFFFF"));
        } else {
            paintDirectionRight.setColor(Color.parseColor("#97694F"));
        }
        // 右箭头
        path4.moveTo(WIDTH_HEIGHT - WIDTH_HEIGHT / 8 - directionWidth + deviationX, WIDTH_HEIGHT / 2 - directionWidth + deviationY);
        path4.lineTo(WIDTH_HEIGHT - WIDTH_HEIGHT / 8 + deviationX, WIDTH_HEIGHT / 2 + deviationY);
        path4.lineTo(WIDTH_HEIGHT - WIDTH_HEIGHT / 8 - directionWidth + deviationX, WIDTH_HEIGHT / 2 + directionWidth + deviationY);
        paintDirectionRight.setStrokeJoin(Paint.Join.BEVEL);

        canvas.drawPath(path4, paintDirectionRight);

        // 长方形 按键直接的间隔
        path5.moveTo(cha,0);
        path5.lineTo(0,cha);
        path5.lineTo(WIDTH_HEIGHT-cha,WIDTH_HEIGHT);
        path5.lineTo(WIDTH_HEIGHT,WIDTH_HEIGHT-cha);
        path5.lineTo(cha,0);
        paintwhite.setStrokeJoin(Paint.Join.BEVEL);
        canvas.drawPath(path5, paintwhite);
        // 长方形 按键直接的间隔
        path5.moveTo(WIDTH_HEIGHT-cha,0);
        path5.lineTo(WIDTH_HEIGHT,cha);
        path5.lineTo(cha,WIDTH_HEIGHT);
        path5.lineTo(0,WIDTH_HEIGHT-cha);
        path5.lineTo(WIDTH_HEIGHT-cha,0);
        paintwhite.setStrokeJoin(Paint.Join.BEVEL);
        canvas.drawPath(path5, paintwhite);

//        if (MTurn) {
//            paintMiddleCircle.setColor(Color.parseColor("#FFFFFF"));
//            paintMiddleCircleBg.setColor(Color.parseColor("#333333"));
//        } else {
//            paintMiddleCircle.setColor(Color.parseColor("#333333"));
//            paintMiddleCircleBg.setColor(Color.parseColor("#FFFFFF"));
//        }

        canvas.drawCircle(WIDTH_HEIGHT / 2 + deviationX, WIDTH_HEIGHT / 2 + deviationY, (float) (WIDTH_HEIGHT / 4.5), paintMiddleCircle);

//        canvas.drawArc(oval, -90 + 25, 360 - 50, false, paintMiddleCircleBg);

//        canvas.drawLine(WIDTH_HEIGHT / 2 + deviationX, WIDTH_HEIGHT / 2 - WIDTH_HEIGHT / 15 - 2 + deviationY,
//                WIDTH_HEIGHT / 2 + deviationX, WIDTH_HEIGHT / 2 - WIDTH_HEIGHT / 15 + 30 + deviationY, paintMiddleCircleBg);
    }

    /**
     * 默认值
     */
    private void cleanDef() {
        upperTurn = false;
        rightTurn = false;
        lowerTurn = false;
        leftTurn = false;
        MTurn = false;
    }

    /**
     * 处理触摸区域
     *
     * @param event 事件
     */
    private void downAndMove(MotionEvent event) {
        // 计算角度
        double MC = Math.atan2(event.getY() - WIDTH_HEIGHT / 2, event.getX() - WIDTH_HEIGHT / 2);
        double MK = 180 * MC / Math.PI;
        // 计算距离
        double MJ = Math.sqrt(Math.pow(event.getX() - WIDTH_HEIGHT / 2, 2) +
                Math.pow(event.getY() - WIDTH_HEIGHT / 2, 2));
        // 清空
        cleanDef();

        // 判断
        if (MJ > WIDTH_HEIGHT / 15 + 20) {
            if (MK < -45 && MK > -180 + 45) {
                upperTurn = true;
                if(wheelTouch!=null) {
                    wheelTouch.upperTurn();
                }
            } else if (MK > -45 && MK < 45) {
                rightTurn = true;
                if(wheelTouch!=null) {
                    wheelTouch.rightTurn();
                }
            } else if (MK > 45 && MK < 180 - 45) {
                lowerTurn = true;
                if(wheelTouch!=null) {
                    wheelTouch.lowerTurn();
                }
            } else {
                leftTurn = true;
                if(wheelTouch!=null) {
                    wheelTouch.leftTurn();
                }
            }
        } else {
            MTurn = true;
            if(wheelTouch!=null) {
                wheelTouch.MTurn();
            }
        }
        // 重绘
        invalidate();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_MOVE:
                break;
            case MotionEvent.ACTION_DOWN:
                downAndMove(event);
                break;
            case MotionEvent.ACTION_UP:
                cleanDef();
                invalidate();
                break;
        }
        return true;
    }
}

interface WheelTouch {
    void upperTurn();
    void lowerTurn();
    void leftTurn();
    void rightTurn();
    void MTurn();
}

3.实例DEMO

方向控制自定义ViewAndroid-Android文档类资源-CSDN下载

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值