自定义仪表盘形状的View

本文详细介绍了一种自定义仪表盘View的绘制方法,包括定义属性、获取属性、重写onMeasure方法及具体绘制过程等内容。

这里写图片描述
写到这里已经写了七篇的自定义View的文章,前段时间朋友让帮忙绘制一个类似仪表盘形状的View,先看整个View的形状有两个圆环,和一个弧,还有线状的指针。

  1. 首先添加需要定义的属性。
    在values下新建一个attr文件添加需要设置属性
<attr name="innerCircleColor" format="color" />
<attr name="innerCircleSize" format="dimension" />
<attr name="outCircleSize" format="dimension" />
<attr name="smallCircleSize" format="dimension" />
<attr name="smallCircleRadiousSize" format="dimension"/>
    <declare-styleable name="CircleView">
        <attr name="innerCircleColor" />
        <attr name="innerCircleSize" />
        <attr name="outCircleSize" />
        <attr name="smallCircleSize" />
        <attr name="smallCircleRadiousSize"/>
    </declare-styleable>

innerCircleColor是内部大圆的颜色,因为整个View只是单一的颜色就单设置了一个颜色。innerCircleSize是内部大圆的宽度,outCircleSize是外部圆弧的宽度,smallCircleRadiousSize是外部空心圆的半径,smallCircleSize是内部实心圆与外部空心圆的距离。
2. 获取自定义的属性。

public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CircleView, defStyleAttr, 0);
int n = a.getIndexCount();
for (int i = 0; i < n; i++) {
int arr = a.getIndex(i);
  switch (arr) {
   case R.styleable.CircleView_innerCircleColor:
    innerColor = a.getColor(arr, Color.BLUE);
                    break;
case R.styleable.CircleView_innerCircleSize:
 innerCircleSize = a.getDimensionPixelSize(arr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 20, getResources().getDisplayMetrics()));
                    break;
 case R.styleable.CircleView_outCircleSize:
outCircleSize = a.getDimensionPixelSize(arr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 20, getResources().getDisplayMetrics()));
                    break;
 case R.styleable.CircleView_smallCircleSize:
  outSmallCircleSize = a.getDimensionPixelSize(arr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 20, getResources().getDisplayMetrics()));
                    break;
 case R.styleable.CircleView_smallCircleRadiousSize:
outSmallCircleRadiousSize = a.getDimensionPixelSize(arr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 20, getResources().getDisplayMetrics()));
                    break;
            }
        }
        a.recycle();
        init();

在init方法里面初始化了画笔和画弧需要的矩形。

   private void init() {
        mInnerPaint = new Paint();
        mOutArcPaint = new Paint();
        mOutCirclePaint = new Paint();
        linePaint = new Paint();
        mOutRectf = new RectF();
    }

3.然后重写onMeasure方法。
都是一样的套路,没什么好说的直接贴代码了。

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        if (widthMode == MeasureSpec.EXACTLY) {
            width = widthSize;
        } else {
            width = getPaddingLeft() + getPaddingRight() + (int) mOutRectf.width();
        }
        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else {
            height = getPaddingBottom() + getPaddingTop() + (int) mOutRectf.height();
        }
        setMeasuredDimension(width, height);
    }

4.最后是onDraw方法里面的具体绘制

  @Override
    protected void onDraw(Canvas canvas) {
        int center = getWidth() / 2;
        canvas.rotate(-45, center, center);
        LinearGradient linearGradient;
        if (unitDrgee < 90) {
 linearGradient = new LinearGradient(0, 0,getMeasuredWidth(), getMeasuredHeight(), gradientColorArray, null, Shader.TileMode.CLAMP);
        } else {
            linearGradient = new LinearGradient(0, 0, getMeasuredWidth(), getMeasuredHeight(), gradientColorArrayother, null, Shader.TileMode.CLAMP);

        }
        mInnerPaint.setShader(linearGradient);
        /**
         * 先画内圆
         */
        mInnerPaint.setAntiAlias(true);
        mInnerPaint.setStyle(Paint.Style.STROKE);
        mInnerPaint.setStrokeWidth(innerCircleSize);
        int radious = center - outSmallCircleRadiousSize * 2 - innerCircleSize / 2;
        canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, radious, mInnerPaint);

/**
 * 外圆
 */
        mOutArcPaint.setShader(linearGradient);
        mOutArcPaint.setAntiAlias(true);
        mOutArcPaint.setStrokeWidth(outCircleSize);
        mOutArcPaint.setStyle(Paint.Style.STROKE);
        mOutRectf.left = outSmallCircleRadiousSize;
        mOutRectf.top = outSmallCircleRadiousSize;
        mOutRectf.right = getWidth() - outSmallCircleRadiousSize;
        mOutRectf.bottom = getHeight() - outSmallCircleRadiousSize;
        mOutArcPaint.setStrokeCap(Paint.Cap.ROUND);
        canvas.drawArc(mOutRectf, 180, unitDrgee, false, mOutArcPaint);
        /**
         * 画刻度线
         */
        linePaint.setColor(Color.WHITE);
        linePaint.setAntiAlias(true);
        linePaint.setStyle(Paint.Style.FILL);
        linePaint.setStrokeWidth(5);
        canvas.drawLine(outSmallCircleRadiousSize * 2, center, outSmallCircleRadiousSize * 2 + (innerCircleSize * 3 / 4), center, linePaint);

        /**
         * 画小圆
         */
        mOutCirclePaint.setShader(linearGradient);
        mOutCirclePaint.setAntiAlias(true);
        mOutCirclePaint.setStyle(Paint.Style.STROKE);
        mOutCirclePaint.setStrokeWidth(10);
        float[] r = calculateBallCenter();
        canvas.drawCircle(r[0], r[1], outSmallCircleRadiousSize, mOutCirclePaint);
        mOutCirclePaint.setStyle(Paint.Style.FILL);
        canvas.drawCircle(r[0], r[1], outSmallCircleRadiousSize - outSmallCircleSize, mOutCirclePaint);
        float angle = 0;
        while (angle < unitDrgee) {
            canvas.drawLine(outSmallCircleRadiousSize * 2 + 10, center, outSmallCircleRadiousSize * 2 + (innerCircleSize * 3 / 4), center, linePaint);
            canvas.rotate(5, center, center);
            angle += 5;
        }
        canvas.drawLine(outSmallCircleRadiousSize * 2, center, outSmallCircleRadiousSize * 2 + (innerCircleSize * 3 / 4), center, linePaint);

    }

首先调用了 canvas.rotate(-45, center, center)的方法,为了使外层圆弧的位置在135度处,这里使画布旋转了-45度角。然后设置了整个View的绘制渐变颜色使用LinearGradient线性渐变的Api。

 if (unitDrgee < 90) {
            linearGradient = new LinearGradient(0, 0, getMeasuredWidth(), getMeasuredHeight(), gradientColorArray, null, Shader.TileMode.CLAMP);
        } else {
            linearGradient = new LinearGradient(0, 0, getMeasuredWidth(), getMeasuredHeight(), gradientColorArrayother, null, Shader.TileMode.CLAMP);

        }
        mInnerPaint.setShader(linearGradient);

,然后画内圆。

  mInnerPaint.setAntiAlias(true);
        mInnerPaint.setStyle(Paint.Style.STROKE);
        mInnerPaint.setStrokeWidth(innerCircleSize);
        int radious = center - outSmallCircleRadiousSize * 2 - innerCircleSize / 2;
        canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, radious, mInnerPaint);

然后画外层圆弧的位置:

 mOutArcPaint.setShader(linearGradient);
        mOutArcPaint.setAntiAlias(true);
        mOutArcPaint.setStrokeWidth(outCircleSize);
        mOutArcPaint.setStyle(Paint.Style.STROKE);
        mOutRectf.left = outSmallCircleRadiousSize;
        mOutRectf.top = outSmallCircleRadiousSize;
        mOutRectf.right = getWidth() - outSmallCircleRadiousSize;
        mOutRectf.bottom = getHeight() - outSmallCircleRadiousSize;
        mOutArcPaint.setStrokeCap(Paint.Cap.ROUND);
        canvas.drawArc(mOutRectf, 180, unitDrgee, false, mOutArcPaint);

然后画刻度线:

 linePaint.setColor(Color.WHITE);
        linePaint.setAntiAlias(true);
        linePaint.setStyle(Paint.Style.FILL);
        linePaint.setStrokeWidth(5);
        canvas.drawLine(outSmallCircleRadiousSize * 2, center, outSmallCircleRadiousSize * 2 + (innerCircleSize * 3 / 4), center, linePaint);

然后分别画外层的大圆和内部的小圆:

 /**
         * 画小圆
         */
        mOutCirclePaint.setShader(linearGradient);
        mOutCirclePaint.setAntiAlias(true);
        mOutCirclePaint.setStyle(Paint.Style.STROKE);
        mOutCirclePaint.setStrokeWidth(10);
        float[] r = calculateBallCenter();
        canvas.drawCircle(r[0], r[1], outSmallCircleRadiousSize, mOutCirclePaint);
        mOutCirclePaint.setStyle(Paint.Style.FILL);
        canvas.drawCircle(r[0], r[1], outSmallCircleRadiousSize - outSmallCircleSize, mOutCirclePaint);

再画外部圆的时候,首先根据旋转的弧度确定圆心的位置:

  /**
     * 计算外圆圆心位置
     *
     * @return
     */
    private float[] calculateBallCenter() {
        float[] center = new float[2];
        float dy = (float) (Math.sin(Math.toRadians(unitDrgee)) * (getWidth() / 2 - outSmallCircleRadiousSize));
        float dx = (float) (Math.cos(Math.toRadians(unitDrgee)) * (getWidth() / 2 - outSmallCircleRadiousSize));
        center[0] = getWidth() / 2 - dx;
        center[1] = getWidth() / 2 - dy;
        return center;
    }

最后便是通过不断的旋转弧度循环画刻度线:

  float angle = 0;
        while (angle < unitDrgee) {
            canvas.drawLine(outSmallCircleRadiousSize * 2 + 10, center, outSmallCircleRadiousSize * 2 + (innerCircleSize * 3 / 4), center, linePaint);
            canvas.rotate(5, center, center);
            angle += 5;
        }

基本的都绘制完之后,这里通过属性动画的完成度做了一个加载动画。:

 private void setAnimation() {
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
        valueAnimator.setDuration(5000);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                float value = (float) valueAnimator.getAnimatedValue();
                percentage = progress * value;
                if (addUpdateListener != null) {
                    addUpdateListener.onAddUpdateListener(value);
                }
                updateProgress();
            }
        });
        valueAnimator.start();
    }

基本重要的代码都说完了,觉得好的可以看下源码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值