自定义一个仪表盘View

先上图

(背景色变化很丑,请忽略哈。主要是演示一下效果哈哈哈!)

分析

首先看到这个效果。分析一下有哪些组成部分

  • 背景
  • 外层的实线圆弧
  • 里层的一圈点
  • 中间两行字

代码

话不多说开撸,先定个小目标,继承个View类再说

public class YbbView extends View {
    private Paint mPaint; //画笔
    private int width; //view的宽
    private int height;//view的高
    
    public YbbView(Context context) {
        super(context);
    }
    public YbbView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }
}
复制代码

重写onMeasure方法

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = getMeasureSize(100, widthMeasureSpec);
        int height = getMeasureSize(100, heightMeasureSpec);
        setMeasuredDimension(width, (int) (height * 0.8f));
    }


    private int getMeasureSize(int def, int measureSpec) {
        int measuresize = 0;
        int mode = MeasureSpec.getMode(measureSpec);
        int size = MeasureSpec.getSize(measureSpec);
        if (mode == MeasureSpec.UNSPECIFIED) {
            measuresize = def;
        } else if (mode == MeasureSpec.EXACTLY) {
            measuresize = size;
        } else if (mode == MeasureSpec.AT_MOST) {
            measuresize = Math.min(def, size);
        }
        return measuresize;
    }
    
      @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        width = w;
        height = (int) (h / 0.8f);
    }
复制代码

emmm,到这可能有人要问了,(没人问我也要说。。) 为啥 (height * 0.8f) 高要乘以0.8呢?其实最开始是没有这个0.8的,不过后来我发现了一个问题,就画个图来表示一下吧~~ 看下图

一个正圆的高度能填满整个View,但是我们只要一部分弧度,所以下面那一部分红色的...姑且叫阴影部分吧,阴影部分就空余出来了,虽然看着只有上面部分,但是View实际的高却要加上阴影部分,很占地方。我开始想过把画布往下移动,但是还是有多余的部分。。最后就想了这么一个办法,可能会损失一些精度。如果有更好的办法请大牛留言指教一下 =_= 。

绘制背景

    /**
     * 绘制view的背景
     */
    private void drawBackground(Canvas canvas) {
        canvas.drawColor(backColor);
    }
复制代码

绘制外层圆弧

    private void drawArc(Canvas canvas) {
        int d = Math.min(width, height);
        float f = TypeValueUtil.dp2px(metrics, 7); /这个弧线距离view四周的margin
        float r = d / 2 - f;
        float left = d / 2 - r;
        float right = d / 2 + r;
        arcRectF = new RectF(left, f, right, d - f);
        mPaint.setStrokeWidth(arcWidth);
        mPaint.setAntiAlias(true);//设置无锯齿
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setShader(getGradient(d, arcColors, startArc));
        canvas.drawArc(arcRectF, startArc, drawEndArc, false, mPaint);
    }
    
    /**
     * 得到一个渐变的圆弧叠加层
     *
     * @param d        圆的直径
     * @param colors   渐变数组
     * @param startArc 从什么角度开始渐变
     * @return
     */
    private SweepGradient getGradient(int d, int[] colors, int startArc) {
        SweepGradient sweepGradient = new SweepGradient(d / 2.0f, d / 2.0f, colors, null);
        //旋转 不然是从0度开始渐变
        Matrix matrix = new Matrix();
        matrix.setRotate(startArc, d / 2.0f, d / 2.0f);
        sweepGradient.setLocalMatrix(matrix);
        return sweepGradient;
    }
复制代码

这里有一个渐变颜色的图层,SweepGradient它默认是从0度开始渐变的,但是我们想要的却不是,所以要进行一个旋转。让他的0度和我们想要的角度重合。

绘制里层一圈刻度

    private void drawPoint(Canvas canvas) {
        float d = Math.min(width, height);
        float f = (int) TypeValueUtil.dp2px(metrics, 18);
        float r = d / 2 - f;
        float left = d / 2 - r;
        float right = d / 2 + r;
        RectF rectF = new RectF(left, f, right, d - f);
        mPaint.setStrokeWidth(pointWidth);
        int startArc = this.startArc;
        for (int i = 0; i <= pointCount; i++) {
            canvas.drawArc(rectF, startArc, pointLength, false, mPaint);
            startArc = startArc + nullLength + pointLength;
        }
    }

复制代码

这里为了可以让刻度可以随意变宽变长所以也用弧度来做。

绘制两行文字

 private void drawText(Canvas canvas) {
        int top = (int) (height / 3 + TypeValueUtil.dp2px(metrics, 15));
        int left = width / 2;
        mPaint.setTextSize(tipTextSize);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setTextAlign(Paint.Align.CENTER);
        String text = "涂山苏苏多少岁?";
        float textW = mPaint.measureText(text);
        LinearGradient lg = new LinearGradient(left, top, left + textW, top, arcColors, null, Shader.TileMode.CLAMP);
        mPaint.setShader(lg);
        canvas.drawText(text, left, top, mPaint);
    }
    
     private void drawNumText(Canvas canvas) {
        int top = (int) (height / 2 + TypeValueUtil.dp2px(metrics, 15));
        int left = width / 2;
        mPaint.setTextSize(amtTextSize);
        String text = format.format(amt);
        float textW = mPaint.measureText(text);
        LinearGradient lg = new LinearGradient(left, top, left + textW, top, arcColors, null, Shader.TileMode.CLAMP);
        mPaint.setShader(lg);
        canvas.drawText(text, left, top, mPaint);
    }
复制代码

重新onDraw方法

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawBackground(canvas);
//        mPaint.setColor(Color.WHITE);
//        mPaint.setStrokeWidth(1);
//        canvas.drawLine(0, height / 2, width, height / 2, mPaint);
//        canvas.drawLine(width / 2, 0, width / 2, height, mPaint);
//        canvas.translate(0, height / 5);
//        canvas.save();
        drawArc(canvas);
        drawPoint(canvas);
//        canvas.restore();
        drawText(canvas);
        drawMoneyText(canvas);

    }
复制代码

onDraw方法里只需要依次调用写好的绘制方法就行了。注释掉的代码是调试用的,画了个坐标轴哈哈本来想删了,想了下留着吧。忽略它就行了。

画好了之后就该让它动起来了。决定采用属性动画来做。

ValueAnimator va = ValueAnimator.ofFloat(numberText);
        va.setDuration(animTime);
        va.setInterpolator(new DecelerateInterpolator());
        va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float currValue = (float) animation.getAnimatedValue();
                numberText = (int) currValue;
                invalidate();
            }
        });
复制代码

这样就让数字动起来了,其他的呢跟这一样的,只是属性值换一个啦。 然后背景色渐变是用到了ArgbEvaluator。看代码:

backColorAnim = ValueAnimator.ofInt(backColors);
        backColorAnim.setDuration(animTime);
        backColorAnim.setEvaluator(new ArgbEvaluator());
        backColorAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                backColor = (int) animation.getAnimatedValue();
                //invalidate();
            }
        });
复制代码

这个类是系统提供关于颜色计算的一个类,有兴趣的可以深入了解一下。

最后定义一些属性,方便使用

<resources>
    <declare-styleable name="YbbView">
        <attr name="arcWidth" format="dimension" /> 
        <attr name="pointArc" format="integer" />
        <attr name="pointSpacing" format="integer" />
        <attr name="pointWidth" format="dimension" />
        <attr name="maxAmt" format="integer" />
        <attr name="animTime" format="integer" />
        <attr name="amtTextSize" format="dimension" />
        <attr name="tipTextSize" format="dimension" />
    </declare-styleable>
</resources>


 public YbbView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.YbbView);
        metrics = getResources().getDisplayMetrics();
        //弧形的宽
        arcWidth = typedArray.getDimension(R.styleable.YbbView_arcWidth, TypeValueUtil.dp2px(metrics, 8));
        //刻度的宽
        pointWidth = typedArray.getDimension(R.styleable.YbbView_pointWidth, TypeValueUtil.dp2px(metrics, 5));
        //刻度多长
        pointLength = typedArray.getInteger(R.styleable.YbbView_pointArc, 1);
        //刻度之间留白
        nullLength = typedArray.getInteger(R.styleable.YbbView_pointSpacing, 3);
        //数字
        numberText = typedArray.getInteger(R.styleable.YbbView_maxAmt, 500000);
        //提示文字
        tipTextSize = typedArray.getDimension(R.styleable.YbbView_tipTextSize, TypeValueUtil.sp2px(metrics, 12));
        //数字的文字大小
        numTextSize = typedArray.getDimension(R.styleable.YbbView_amtTextSize, TypeValueUtil.sp2px(metrics, 30));
        //动画的时间
        animTime = typedArray.getInteger(R.styleable.YbbView_animTime, 1000);
    }
复制代码

结束

emmm 可能有些粗糙,不过这么写下来也是学到了很多知识。希望有大牛能多指正我的不足之处。多交流学习。 最后上 Github

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值