Android 自定义刻度仪表MeterView

原创
2020/07/17 14:53
阅读数 1.3K

效果预览

 

代码实现

public class AutoMeterView extends View {


    private  DisplayMetrics displayMetrics;
    private TextPaint mPaint;
    private int lineWidth  = 10;
    private int outlineWidth  = 20;
    private int textSize = 16;
    private float sumDegree = (360-90.0f);

    float degreeOffset = 0;

    private ValueAnimator mDegreeAnimator;

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

    public AutoMeterView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public AutoMeterView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        displayMetrics = context.getResources().getDisplayMetrics();
        initPaint();
    }

    private void initPaint() {
        // 实例化画笔并打开抗锯齿
        mPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG );
        mPaint.setAntiAlias(true);
        mPaint.setTextSize(dpTopx(textSize));

    }
    private float dpTopx(int dp){
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dp,getResources().getDisplayMetrics());
    }

    public void setSumDegree(float sumDegree) {
        this.sumDegree = sumDegree;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);

        if(widthMode!=MeasureSpec.EXACTLY){
            widthSize = displayMetrics.widthPixels/2;
        }

        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        if(heightMode!=MeasureSpec.EXACTLY){
            heightSize = displayMetrics.widthPixels/2;
        }
        widthSize = heightSize = Math.min(widthSize,heightSize);

       setMeasuredDimension(widthSize,heightSize);
    }


    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

    }

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

        int width = getWidth();
        int height = getWidth();

        mPaint.setStyle(Paint.Style.STROKE);

        mPaint.setStrokeWidth(dpTopx(outlineWidth));
        float strokeWidth = mPaint.getStrokeWidth();

        int outRadius = (int) (Math.min(width/2,height/2));
        int innerRadius = (int) (outRadius - 3*strokeWidth);
        int innerDotRadius = (int) (outRadius - 2*strokeWidth);

        float perDegree = sumDegree/100;

        canvas.save();
        canvas.translate(width/2,height/2);
        canvas.rotate(90+45);


        mPaint.setColor(0xff54d68c);
        RectF rectF = new RectF(-width/2+strokeWidth/2,-height/2+strokeWidth/2,width/2-strokeWidth/2,height/2-strokeWidth/2);
        canvas.drawArc(rectF,0, 20*perDegree,false,mPaint);

        mPaint.setColor(0xffff9922);
        canvas.drawArc(rectF,20*perDegree, 60*perDegree,false,mPaint);


        mPaint.setColor(Color.RED);
        canvas.drawArc(rectF,80*perDegree, 20*perDegree,false,mPaint);

        mPaint.setStrokeWidth(dpTopx(lineWidth));
        strokeWidth = mPaint.getStrokeWidth();
        outRadius = (int) (Math.min(width/2,height/2));
        innerRadius = (int) (outRadius - 4*strokeWidth);
        innerDotRadius = (int) (outRadius - 3*strokeWidth);

        for (int i=0;i<=100;i++){

            float innerLineRadius = innerDotRadius;
            if(i%10==0){
                mPaint.setStrokeWidth(strokeWidth/4);
                innerLineRadius = innerRadius;
            }else{
                mPaint.setStrokeWidth(strokeWidth/5);
            }

            float startX = (float) (innerLineRadius*Math.cos(Math.toRadians(i*perDegree)));
            float startY = (float)(innerLineRadius*Math.sin(Math.toRadians(i*perDegree)));

            float endX = (float)(outRadius*Math.cos(Math.toRadians(i*perDegree)));
            float endY = (float)(outRadius*Math.sin(Math.toRadians(i*perDegree)));

            mPaint.setColor(getPaintColor(i));
            canvas.drawLine(startX,startY,endX,endY,mPaint);

        }

        canvas.restore();

        canvas.save();
        mPaint.setStyle(Paint.Style.FILL);
        canvas.translate(width/2,height/2);
        canvas.rotate(-90-45);
        //该方法旋转的是坐标系,而不是图层

        for (int i=0;i<=10;i++){
            if(i>0) {
                canvas.rotate(perDegree * 10);
                //每次旋转坐标系一定的位置
            }
            mPaint.setColor(getPaintColor(i*10));
            String text = String.valueOf(i*10);
            Rect bounds = new Rect();
            mPaint.getTextBounds(text, 0, text.length(), bounds);
            float textBaseline = innerRadius-strokeWidth - bounds.height() + getTextPaintBaseline(mPaint);
            canvas.drawText(text,0-bounds.width()/2,-textBaseline,mPaint);
            //从y轴负方向开始绘制

        }
        canvas.restore();


        canvas.save();
        canvas.translate(width/2,height/2);


        mPaint.setColor(getPaintColor(degreeOffset));

        int tail = (int) dpTopx(outlineWidth);

        float startX = (float) (-tail*Math.cos(Math.toRadians(90+45+degreeOffset*perDegree)));
        float startY = (float)(-tail*Math.sin(Math.toRadians(90+45+degreeOffset*perDegree)));

        float endX = (float)((innerRadius -tail )*Math.cos(Math.toRadians(90+45+degreeOffset*perDegree)));
        float endY = (float)((innerRadius -tail )*Math.sin(Math.toRadians(90+45+degreeOffset*perDegree)));

        canvas.drawLine(startX,startY,endX,endY,mPaint);

        canvas.drawCircle(0,0,tail/2,mPaint);

        canvas.restore();

    }

    private int getPaintColor(float degreeOffset) {
        if (degreeOffset <= 20) {
            return 0xff54d68c;
        } else if (degreeOffset > 20 && degreeOffset < 80) {
            return 0xffff9922;
        } else {
            return Color.RED;
        }
    }

    /**
     * 基线到中线的距离=(Descent+Ascent)/2-Descent
     注意,实际获取到的Ascent是负数。公式推导过程如下:
     中线到BOTTOM的距离是(Descent+Ascent)/2,这个距离又等于Descent+中线到基线的距离,即(Descent+Ascent)/2=基线到中线的距离+Descent。
     */
    public static float getTextPaintBaseline(Paint p) {
        Paint.FontMetrics fontMetrics = p.getFontMetrics();
        return (fontMetrics.descent - fontMetrics.ascent) / 2 -fontMetrics.descent;
    }


    public void setDegreeOffset(float targetDegreeOffset) {
        if(targetDegreeOffset<0){
            targetDegreeOffset  = 0;
        }
        if(targetDegreeOffset>100){
            targetDegreeOffset  = 100;
        }

        if(this.degreeOffset==targetDegreeOffset){
            return;
        }

        if(getWidth()==0|| getHeight()==0){
            this.degreeOffset = targetDegreeOffset;
            return;
        }

        if(mDegreeAnimator!=null){
            mDegreeAnimator.cancel();
        }
        float perDegree = sumDegree/100;

        float bounceOffset = 0;
        if(this.degreeOffset>targetDegreeOffset){
            bounceOffset = targetDegreeOffset - perDegree*5;
            if(bounceOffset<0){
                bounceOffset = 0;
            }
        }else{
            bounceOffset = targetDegreeOffset + perDegree*5;
            if(bounceOffset>100){
                bounceOffset = 100;
            }
        }
        mDegreeAnimator = ValueAnimator.ofFloat(this.degreeOffset,bounceOffset,targetDegreeOffset);
        mDegreeAnimator.setDuration(500);
        mDegreeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                postUpdateDegree((Float) animation.getAnimatedValue());
            }


        });
        mDegreeAnimator.start();
    }

    private void postUpdateDegree(float animatedValue) {
        this.degreeOffset = animatedValue;
        postInvalidate();
    }
}

 

展开阅读全文
加载中

作者的其它热门文章

打赏
0
0 收藏
分享
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部