之前工作中有用到环形进度条等的,为了赶进度都是在网上找到相似的效果的然后再进行修改。一直都想自己画一个,今天就和大家一起来学习刻度盘的绘制。

先看一下截图:

android 画一个圆点 android绘制半圆弧线_环形进度条

 效果演示请看 刻度盘演示

代码下载:CalibrationView.zip

在文章后面也会贴出全部代码

实现思路主要有:

1.确定中心点坐标

2.画出背景圆弧以及刻度点

3.画出实际进度值及刻度点

4.画指针

5.增加动画

注意:因为是画上半圆弧,及上下方向只有圆的一半,所以需要根据宽度将高度重新调整为宽度的一半

实现:

第一步,确定中心点:

在onSizeChanged方法中得到控件调整过之后的宽高,将中心点定在(w/2,h),处,初始化圆弧宽带为高度的一半,以及圆弧半径等,代码如下:

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

        centerX = w / 2;//中心点x坐标
        centerY = h;//中心点y坐标
        arcWidth = h / 2;//圆弧宽带

        w_r = h - arcWidth - 20; //外圈半径 = 高度 - 圆弧宽度 - 20(去掉一点,一面刚好顶大view的边上)
    }

第二步,画背景圆弧和刻度点:

a,圆弧:

先根据中心点坐标在画布上画出一个矩形,后续的圆弧之类的都在这里画:

RectF rf = new RectF(0, 0, viewW, viewH);
        RectF rf1 = new RectF(centerX - w_r - arcWidth / 2, centerY - w_r - arcWidth / 2, centerX + w_r + arcWidth / 2, centerY - (-w_r - arcWidth / 2));
        Paint p = new Paint();
        p.setStyle(Paint.Style.STROKE);
        p.setColor(Color.WHITE);
        canvas.drawRect(rf1, p);

然后通过圆弧半径,将圆弧宽度设置为画圆弧的paint的宽度,确定起始位置以及圆弧的角度开始画圆弧,代码如下:

/**
     * 画背景灰色圆形和点
     *
     * @param canvas
     * @param rf
     */
    private void drawBg(Canvas canvas, RectF rf) {
        p_bc.setStrokeWidth(arcWidth);
        canvas.drawArc(rf, -180, 180, false, p_bc);//从-180度开始画一段180度的弧形,0度是3点钟方向
        drawPoint(rf, canvas);

    }

最后画圆形的刻度点:

这里要画的是上半段圆弧,对应180度,这里将180度分成10分,每份18度,0和180度不需要画刻度点,则需要画9个刻度点,中间一个正好是90度的,然后根据圆弧宽度,圆弧半径以及直角三角形的边角对应关系进行计算,得出每个刻度点的中心位置,然后画点,代码如下:

/**
     * 画刻度点
     *
     * @param rf
     * @param canvas
     */
    private void drawPoint(RectF rf, Canvas canvas) {
        /**
         * 画180度的上弧形作为刻度盘
         * 分成10分,每份18度
         * 刻度点只需要画1-9个,即最边上的两个不需要画
         *
         * 已知圆弧的半径,可以根据知道直角三角形斜边及角C,计算出每个刻度的的位置,然后画点
         *
         * ps  直角三角形已知斜边c和对应角,其他两边分别为c*sin(C)与c*cos(C)
         */
        for (int i = 1; i < 10; i++) {
            int d = i * 18;
            if (d > 90) {
                d = 180 - d;
            }
            float degree = (float) (Math.PI * (d * 1.0f / 180));
            float y = (float) (centerY - ((w_r + arcWidth / 2) * 1.0f * Math.sin(degree)));
            float x = 0.0f;

            if (i < 5) {
                x = (float) (centerX - ((w_r + arcWidth / 2) * 1.0f * Math.cos(degree)));
            } else if (i > 5) {
                x = (float) (centerX + ((w_r + arcWidth / 2) * 1.0f * Math.cos(degree)));
            } else {
                y = centerY - w_r - arcWidth / 2;
                x = centerX;
            }

            p_circle.setStrokeWidth(20);
            canvas.drawCircle(x, y, p_r, p_circle);

//            canvas.drawPoint(x,y,p_circle);
        }
    }

第三步,画实际刻度弧度以及再将刻度点画出来:

画实际刻度弧度,根据实际进度/最大进度*180得到当前需要画实际弧度的角度是多大,degree_total;

然后计算这么多角度中分几种不同的颜色断,我现在的是每3个刻度点间是同一种颜色,及每36度是一种颜色,然后每种颜色分别对应多少角度计算出来,分段画圆弧,代码如下:

float dgree_total = 0.0f;

    /**
     * 画实际刻度
     *
     * @param canvas
     * @param rf
     */
    private void drawRealProgress(Canvas canvas, RectF rf) {
        float startArg = -180f;//记录上次画到的角度,下次从这个角度开始画,以免覆盖了
        /**
         * 根据当前进度值和总进度值计算出当前的总角度,原始设置角度为180
         */
        dgree_total = progress * (180f / maxProgress);//计算出当前刻度值对应应该转动的角度
        int count = (int) (dgree_total / 18);//得到这个角度对应能转过几个点,好根据不同范围分次画不同的颜色
        float last = dgree_total % 18;
        if (last != 0) {
            count += 1;
        }

        p_real.setStrokeWidth(arcWidth);
        p_real.setStyle(Paint.Style.STROKE);
        p_real.setAntiAlias(true);
        for (int i = 0; i < count; i++) {//进行分段画弧
            p_real.setColor(gradientColors[i / 2]);//每两个点范围内的颜色相同,故这样取颜色
            float lastd = 18 * (i + 1) * 1.0f > dgree_total ? (dgree_total - 18 * i * 1.0f) : 18;


            canvas.drawArc(rf, startArg - 0.5f, lastd + 0.5f, false, p_real);//每次往前画一点,有空隙
            startArg += 18;
        }
        drawPoint(rf, canvas);//画完之后刻度点被覆盖了,重新补上
    }

这样的话会将背景圆弧以及原来的刻度点覆盖掉,重新安照第二步再次将刻度点补上。

第四步,画指针:

思路,画一边大,一边小的指针,在中心点处画一个小圆,以根据角度移动小圆上直径对应的两个点以及圆弧上实际角度对应的最外面的点画三角形(这三个点都是通过直角三角形的边角关系进行计算得来的),就是指针了,代码如下:

/**
     * 画指针
     *
     * @param canvas
     * @param rf1
     */
    private void drawPointer(Canvas canvas, RectF rf1) {
        /**
         * 此处同样根据当前的角度计算出对应圆弧上的点,
         * 在圆心出划出指针的粗的部分(一个)小圆
         * 以小圆直径外的俩个点以及计算出的圆弧上的点通过路径画出图形
         * 即为指针
         */
        int mr = 16;//小圆的半径
        int mx = centerX;//小圆的圆心坐标x
        int my = centerY - 16;//小圆的圆心坐标y
        p_pointer.setStrokeWidth(mr);//以圆半径画的圈为中心平分,内8外8
        //画圆心在(mx,my)的小圆
        canvas.drawCircle(mx, my, mr / 2, p_pointer);

        float x = 0.0f;//某个进度值对应的外圆弧上的点的x坐标
        float y = 0.0f;//某个进度值对应的外圆弧上的点的y坐标
        int count = (int) (dgree_total % 90 / 18);
        Point p1 = new Point(mx + mr, my);//初始进度值是0的时候的小圆上的点1
        Point p2 = new Point(mx - mr, my);//初始进度值是0的时候的小圆上的点2
        if (dgree_total > 90) {//某个值对应的角度大于90度的时候,计算对应的x与y的所对应的三角函数不同,所以以90度作为界点分别计算
            float md = 180 - dgree_total;
            x = (float) (centerX + (w_r + arcWidth ) * Math.cos(Math.PI * md / 180.0f));
            y = (float) (centerY - (w_r + arcWidth ) * Math.sin(Math.PI * md / 180.0f));
            p1.x = (int) (mx - mr * Math.cos(Math.PI * (90 - md) / 180.0f));
            p1.y = (int) (my - mr * Math.sin(Math.PI * (90 - md) / 180.0f));
            p2.x = (int) (mx + mr * Math.cos(Math.PI * (90 - md) / 180.0f));
            p2.y = (int) (my + mr * Math.sin(Math.PI * (90 - md) / 180.0f));
        } else {

            x = (float) (centerX - (w_r + arcWidth ) * Math.cos(Math.PI * dgree_total / 180.0f));
            y = (float) (centerY - (w_r + arcWidth ) * Math.sin(Math.PI * dgree_total / 180.0f));
            p1.x = (int) (mx - mr * Math.sin(Math.PI * dgree_total / 180.0f));
            p1.y = (int) (my + mr * Math.cos(Math.PI * dgree_total / 180.0f));
            p2.x = (int) (mx + mr * Math.sin(Math.PI * dgree_total / 180.0f));
            p2.y = (int) (my - mr * Math.cos(Math.PI * dgree_total / 180.0f));

        }

        if (dgree_total == 0) {
            x = mx - w_r - arcWidth / 2;
            y = mx;
            p1.x = mx;
            p1.y = my - mr;
            p2.x = mx;
            p2.y = my + mr;
        }
        Path path = new Path();
        path.moveTo(x, y);
        path.lineTo(p1.x, p1.y);

        path.lineTo(p2.x, p2.y);
        Paint p = new Paint();
        p.setColor(pointerColor);
        p.setAntiAlias(true);
        p.setStrokeWidth(12);
        p.setStyle(Paint.Style.FILL);
        path.close();
        canvas.drawPath(path, p);
    }

第五步,加动画

        通过ValueAnimator给view添加动画展示。可以通过当前进度与原进度的比例计算出需要加载动画的时间,而不是每次都那么长时间,然后从原进度到当前进度进行动画绘制,代码如下:

/**
     * 动画画进度
     * @param v1
     * @param value
     */
    private void StartAnim(int v1, int value) {
        //根据当前所需要画的进度值计算出需要执行动画的时间,以免不管多少进度都执行animDuration 毫秒
        int du = 2000 * (Math.abs(v1 - value)) / maxProgress;


        animator = ValueAnimator.ofInt(v1, value).setDuration(du);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                progress = (int) animator.getAnimatedValue();
                dgree_total = 0f;
                invalidate();
            }
        });
        animator.start();
    }

再来看一下怎么将宽高比强制设置得超过2:1的,代码如下:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = 0;
        int heightSize = 0;

        if (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED) {
            widthSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEF_VIEW_SIZE, getResources().getDisplayMetrics());
            widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
        } else {
            widthSize = getMeasuredWidth();
        }

        if (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) {
            heightSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEF_VIEW_SIZE, getResources().getDisplayMetrics());
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
        } else {
            heightSize = getMeasuredHeight();
        }

        if (widthSize != 0 && heightSize != 0 && widthSize / 2 <heightSize) {//画的是上半圆弧,所以需要宽度超过高度的2倍保证画出来的图像不留空隙
            heightSize = widthSize / 2;
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    }

然后讲一下,用了一些自定义的xml属性,如果不需要的话可以去掉,通过setter方法动态设置也是可以的,代码:

public CalibrationView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        p_bc = new Paint();
        p_real = new Paint();
        p_pointer = new Paint();
        p_circle = new Paint();

        //根据自定义属性获取相关的属性值,可以不要,根据对应的setter方法进行设置或者不设置都行,都与默认值
        TypedArray array=context.obtainStyledAttributes(attrs,R.styleable.CalibrationView);
        maxProgress=array.getInt(R.styleable.CalibrationView_MaxProgress,100);
        showProGress=array.getInt(R.styleable.CalibrationView_DefaultShow,0);
        isFromZero=array.getBoolean(R.styleable.CalibrationView_ShowFromZero,false);
        shouldShowAnim=array.getBoolean(R.styleable.CalibrationView_ShowAnim,true);
        animDuration=array.getInt(R.styleable.CalibrationView_AnimDuration,2000);

        pointerColor=array.getColor(R.styleable.CalibrationView_PointerColor,Color.parseColor("#505a78"));
        bgColor=array.getColor(R.styleable.CalibrationView_CalibBgColor,Color.parseColor("#ECEFF4"));
        calibCircleColor=array.getColor(R.styleable.CalibrationView_CalibCircleColor,Color.WHITE);
        //根据自定义属性获取相关的属性值,可以不要,根据对应的setter方法进行设置或者不设置都行,都与默认值

        p_bc.setColor(bgColor);
        p_bc.setStyle(Paint.Style.STROKE);
        p_bc.setAntiAlias(true);//去噪

        p_pointer.setColor(pointerColor);
        p_pointer.setStyle(Paint.Style.STROKE);
        p_pointer.setAntiAlias(true);//去噪

        p_circle.setColor(calibCircleColor);
        p_circle.setStyle(Paint.Style.STROKE);
        p_circle.setStrokeWidth(p_r);
        p_circle.setAntiAlias(true);//去噪
        StartAnim(0, showProGress);
    }

代码:

CalibrationView
package cn.humanetplan.mydemos;

import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;

/**
 * author   : 肖波
 * e-mail   : xiaoboabc168@163.com
 * date     :  2019/1/3.
 */

public class CalibrationView extends View {
    private int bgColor = Color.parseColor("#ECEFF4"); //背景颜色
    //渐变颜色
    private int[] gradientColors = new int[]{
            Color.parseColor("#3fe0d0"),
            Color.parseColor("#3b3b43"),
            Color.parseColor("#505a78"),
            Color.parseColor("#ff676b"),
            Color.RED
    };
    private int progress = 0;//在动画中接收实时的进度,在ondraw中用
    private int showProGress = 50;//要显示的进度
    private boolean isFromZero = false;//设置是否每次都从0开始计算
    private boolean shouldShowAnim=true;

    private int maxProgress = 100;//默认总进度为100
    private int pointerColor = Color.parseColor("#505a78");//指针颜色
    private int calibCircleColor=Color.WHITE;

    private Paint p_bc;//画背景的画笔
    private Paint p_real;//画实际颜色的画笔
    private Paint p_pointer;//画指针的画笔
    private Paint p_circle;//画小圆点的画笔

    private int DEF_VIEW_SIZE = 300;//默认view大小

    private int w_r = 300, n_r = 50, p_r = 10, c_r = 8;

    private int centerX = 500;//中心点x
    private int centerY = 500;//中心点y

    private int arcWidth = 200;//弧形的宽度,通过paint的setStrokeWidth来设置
    private int animDuration=2000;//默认整个进度画完需要执行动画的时间

    public void setCalibCircleColor(int calibCircleColor) {
        this.calibCircleColor = calibCircleColor;
    }

    public void setBgColor(int bgColor) {
        this.bgColor = bgColor;
        invalidate();
    }

    public void setShouldShowAnim(boolean shouldShowAnim) {
        this.shouldShowAnim = shouldShowAnim;
    }

    public void setMaxProgress(int maxProgress) {
        this.maxProgress = maxProgress;
    }

    public void setPointerColor(int pointerColor) {
        this.pointerColor = pointerColor;
    }

    public void setAnimDuration(int animDuration) {
        this.animDuration = animDuration;
    }

    public void setFromZero(boolean fromZero) {
        isFromZero = fromZero;
    }

    public void setShowProGress(int showProGress) {
        int v1 = this.showProGress;
        this.showProGress = showProGress;

        if (shouldShowAnim){
            if (isFromZero) {
                StartAnim(0, showProGress);
            } else
                StartAnim(v1, showProGress);
        }else {
            progress=showProGress;
            invalidate();
        }



    }

    public CalibrationView(Context context) {
        this(context, null);

    }

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

    public CalibrationView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        p_bc = new Paint();
        p_real = new Paint();
        p_pointer = new Paint();
        p_circle = new Paint();

        //根据自定义属性获取相关的属性值,可以不要,根据对应的setter方法进行设置或者不设置都行,都与默认值
        TypedArray array=context.obtainStyledAttributes(attrs,R.styleable.CalibrationView);
        maxProgress=array.getInt(R.styleable.CalibrationView_MaxProgress,100);
        showProGress=array.getInt(R.styleable.CalibrationView_DefaultShow,0);
        isFromZero=array.getBoolean(R.styleable.CalibrationView_ShowFromZero,false);
        shouldShowAnim=array.getBoolean(R.styleable.CalibrationView_ShowAnim,true);
        animDuration=array.getInt(R.styleable.CalibrationView_AnimDuration,2000);

        pointerColor=array.getColor(R.styleable.CalibrationView_PointerColor,Color.parseColor("#505a78"));
        bgColor=array.getColor(R.styleable.CalibrationView_CalibBgColor,Color.parseColor("#ECEFF4"));
        calibCircleColor=array.getColor(R.styleable.CalibrationView_CalibCircleColor,Color.WHITE);
        //根据自定义属性获取相关的属性值,可以不要,根据对应的setter方法进行设置或者不设置都行,都与默认值

        p_bc.setColor(bgColor);
        p_bc.setStyle(Paint.Style.STROKE);
        p_bc.setAntiAlias(true);//去噪

        p_pointer.setColor(pointerColor);
        p_pointer.setStyle(Paint.Style.STROKE);
        p_pointer.setAntiAlias(true);//去噪

        p_circle.setColor(calibCircleColor);
        p_circle.setStyle(Paint.Style.STROKE);
        p_circle.setStrokeWidth(p_r);
        p_circle.setAntiAlias(true);//去噪
        StartAnim(0, showProGress);
    }

    int viewH = 300;
    int viewW = 300;

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

        centerX = w / 2;//中心点x坐标
        centerY = h;//中心点y坐标
        arcWidth = h / 2;//圆弧宽带

        w_r = h - arcWidth - 20; //外圈半径 = 高度 - 圆弧宽度 - 20(去掉一点,一面刚好顶大view的边上)
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = 0;
        int heightSize = 0;

        if (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED) {
            widthSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEF_VIEW_SIZE, getResources().getDisplayMetrics());
            widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
        } else {
            widthSize = getMeasuredWidth();
        }

        if (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) {
            heightSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEF_VIEW_SIZE, getResources().getDisplayMetrics());
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
        } else {
            heightSize = getMeasuredHeight();
        }

        if (widthSize != 0 && heightSize != 0 && widthSize / 2 <heightSize) {//画的是上半圆弧,所以需要宽度超过高度的2倍保证画出来的图像不留空隙
            heightSize = widthSize / 2;
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
//        Log.i("TestView","onDraw");
//        RectF rf = new RectF(0, 0, viewW, viewH);
        RectF rf1 = new RectF(centerX - w_r - arcWidth / 2, centerY - w_r - arcWidth / 2, centerX + w_r + arcWidth / 2, centerY - (-w_r - arcWidth / 2));
        Paint p = new Paint();
        p.setStyle(Paint.Style.STROKE);
        p.setColor(Color.WHITE);
        canvas.drawRect(rf1, p);
        drawBg(canvas, rf1);//画背景弧形和圆点
        canvas.save();
        drawRealProgress(canvas, rf1);//画实际进度
        canvas.save();
        drawPointer(canvas, rf1);//画指针

    }

    /**
     * 画指针
     *
     * @param canvas
     * @param rf1
     */
    private void drawPointer(Canvas canvas, RectF rf1) {
        /**
         * 此处同样根据当前的角度计算出对应圆弧上的点,
         * 在圆心出划出指针的粗的部分(一个)小圆
         * 以小圆直径外的俩个点以及计算出的圆弧上的点通过路径画出图形
         * 即为指针
         */
        int mr = 16;//小圆的半径
        int mx = centerX;//小圆的圆心坐标x
        int my = centerY - 16;//小圆的圆心坐标y
        p_pointer.setStrokeWidth(mr);//以圆半径画的圈为中心平分,内8外8
        //画圆心在(mx,my)的小圆
        canvas.drawCircle(mx, my, mr / 2, p_pointer);

        float x = 0.0f;//某个进度值对应的外圆弧上的点的x坐标
        float y = 0.0f;//某个进度值对应的外圆弧上的点的y坐标
        int count = (int) (dgree_total % 90 / 18);
        Point p1 = new Point(mx + mr, my);//初始进度值是0的时候的小圆上的点1
        Point p2 = new Point(mx - mr, my);//初始进度值是0的时候的小圆上的点2
        if (dgree_total > 90) {//某个值对应的角度大于90度的时候,计算对应的x与y的所对应的三角函数不同,所以以90度作为界点分别计算
            float md = 180 - dgree_total;
            x = (float) (centerX + (w_r + arcWidth ) * Math.cos(Math.PI * md / 180.0f));
            y = (float) (centerY - (w_r + arcWidth ) * Math.sin(Math.PI * md / 180.0f));
            p1.x = (int) (mx - mr * Math.cos(Math.PI * (90 - md) / 180.0f));
            p1.y = (int) (my - mr * Math.sin(Math.PI * (90 - md) / 180.0f));
            p2.x = (int) (mx + mr * Math.cos(Math.PI * (90 - md) / 180.0f));
            p2.y = (int) (my + mr * Math.sin(Math.PI * (90 - md) / 180.0f));
        } else {

            x = (float) (centerX - (w_r + arcWidth ) * Math.cos(Math.PI * dgree_total / 180.0f));
            y = (float) (centerY - (w_r + arcWidth ) * Math.sin(Math.PI * dgree_total / 180.0f));
            p1.x = (int) (mx - mr * Math.sin(Math.PI * dgree_total / 180.0f));
            p1.y = (int) (my + mr * Math.cos(Math.PI * dgree_total / 180.0f));
            p2.x = (int) (mx + mr * Math.sin(Math.PI * dgree_total / 180.0f));
            p2.y = (int) (my - mr * Math.cos(Math.PI * dgree_total / 180.0f));

        }

        if (dgree_total == 0) {
            x = mx - w_r - arcWidth / 2;
            y = mx;
            p1.x = mx;
            p1.y = my - mr;
            p2.x = mx;
            p2.y = my + mr;
        }
        Path path = new Path();
        path.moveTo(x, y);
        path.lineTo(p1.x, p1.y);

        path.lineTo(p2.x, p2.y);
        Paint p = new Paint();
        p.setColor(pointerColor);
        p.setAntiAlias(true);
        p.setStrokeWidth(12);
        p.setStyle(Paint.Style.FILL);
        path.close();
        canvas.drawPath(path, p);
    }

    /**
     * 画刻度点
     *
     * @param rf
     * @param canvas
     */
    private void drawPoint(RectF rf, Canvas canvas) {
        /**
         * 画180度的上弧形作为刻度盘
         * 分成10分,每份18度
         * 刻度点只需要画1-9个,即最边上的两个不需要画
         *
         * 已知圆弧的半径,可以根据知道直角三角形斜边及角C,计算出每个刻度的的位置,然后画点
         *
         * ps  直角三角形已知斜边c和对应角,其他两边分别为c*sin(C)与c*cos(C)
         */
        for (int i = 1; i < 10; i++) {
            int d = i * 18;
            if (d > 90) {
                d = 180 - d;
            }
            float degree = (float) (Math.PI * (d * 1.0f / 180));
            float y = (float) (centerY - ((w_r + arcWidth / 2) * 1.0f * Math.sin(degree)));
            float x = 0.0f;

            if (i < 5) {
                x = (float) (centerX - ((w_r + arcWidth / 2) * 1.0f * Math.cos(degree)));
            } else if (i > 5) {
                x = (float) (centerX + ((w_r + arcWidth / 2) * 1.0f * Math.cos(degree)));
            } else {
                y = centerY - w_r - arcWidth / 2;
                x = centerX;
            }

            p_circle.setStrokeWidth(20);
            canvas.drawCircle(x, y, p_r, p_circle);

//            canvas.drawPoint(x,y,p_circle);
        }
    }


    /**
     * 画背景灰色圆形和点
     *
     * @param canvas
     * @param rf
     */
    private void drawBg(Canvas canvas, RectF rf) {
        p_bc.setStrokeWidth(arcWidth);
        canvas.drawArc(rf, -180, 180, false, p_bc);//从-180度开始画一段180度的弧形,0度是3点钟方向
        drawPoint(rf, canvas);

    }

    float dgree_total = 0.0f;

    /**
     * 画实际刻度
     *
     * @param canvas
     * @param rf
     */
    private void drawRealProgress(Canvas canvas, RectF rf) {
        float startArg = -180f;//记录上次画到的角度,下次从这个角度开始画,以免覆盖了
        /**
         * 根据当前进度值和总进度值计算出当前的总角度,原始设置角度为180
         */
        dgree_total = progress * (180f / maxProgress);//计算出当前刻度值对应应该转动的角度
        int count = (int) (dgree_total / 18);//得到这个角度对应能转过几个点,好根据不同范围分次画不同的颜色
        float last = dgree_total % 18;
        if (last != 0) {
            count += 1;
        }

        p_real.setStrokeWidth(arcWidth);
        p_real.setStyle(Paint.Style.STROKE);
        p_real.setAntiAlias(true);
        for (int i = 0; i < count; i++) {//进行分段画弧
            p_real.setColor(gradientColors[i / 2]);//每两个点范围内的颜色相同,故这样取颜色
            float lastd = 18 * (i + 1) * 1.0f > dgree_total ? (dgree_total - 18 * i * 1.0f) : 18;


            canvas.drawArc(rf, startArg - 0.5f, lastd + 0.5f, false, p_real);//每次往前画一点,有空隙
            startArg += 18;
        }
        drawPoint(rf, canvas);//画完之后刻度点被覆盖了,重新补上
    }
    ValueAnimator animator;

    /**
     * 动画画进度
     * @param v1
     * @param value
     */
    private void StartAnim(int v1, int value) {
        //根据当前所需要画的进度值计算出需要执行动画的时间,以免不管多少进度都执行animDuration 毫秒
        int du = 2000 * (Math.abs(v1 - value)) / maxProgress;


        animator = ValueAnimator.ofInt(v1, value).setDuration(du);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                progress = (int) animator.getAnimatedValue();
                dgree_total = 0f;
                invalidate();
            }
        });
        animator.start();
    }
}

attrs:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CalibrationView">
        <!--最大进度值-->
        <attr name="MaxProgress" format="integer"/>
        <!--默认显示值-->
        <attr name="DefaultShow" format="integer"/>
        <!--是否显示动画-->
        <attr name="ShowAnim" format="boolean"/>
        <!--动画时是否从0开始-->
        <attr name="ShowFromZero" format="boolean"/>
        <!--动画的执行总时间-->
        <attr name="AnimDuration" format="integer"/>
       <!--弧形的背景颜色-->
        <attr name="CalibBgColor" format="color"/>
        <!--指针的颜色-->
        <attr name="PointerColor" format="color"/>
        <!--圆形刻度点的颜色-->
        <attr name="CalibCircleColor" format="color"/>


    </declare-styleable>
</resources>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="cn.humanetplan.mydemos.MainActivity">

    <cn.humanetplan.mydemos.CalibrationView
        android:layout_marginTop="5dp"
        android:id="@+id/testView"
        android:layout_width="280dp"
        app:PointerColor="#0000ff"
        app:DefaultShow="0"
        app:ShowAnim="true"
        android:layout_gravity="center_horizontal"
        android:layout_height="300dp" />

    <TextView
        android:id="@+id/tv_value"
        android:layout_width="wrap_content"
        android:layout_marginTop="3dp"
        android:textSize="16sp"
        android:layout_gravity="center_horizontal"
        android:text="0.400 "
        android:layout_height="wrap_content" />
</LinearLayout>
MainActivity:
package cn.humanetplan.mydemos;

import android.animation.ValueAnimator;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    CalibrationView testView;
    TextView tv_value;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        testView=findViewById(R.id.testView);
        tv_value=findViewById(R.id.tv_value);
//        testView.setShowProGress(0);
//        testView.setFromZero(true);
    }

    Handler handler=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            float p= (float) msg.obj;
            setValueAnimator(p);
            lastp=p;
            testView.setShowProGress((int) p);

//            tv_value.setText(p/100+"  Mpa");

//            Log.i("TestView ","p  =  "+p);


        }
    };
    float lastp=0.0f;

    private void setValueAnimator(float p){
        int d= (int) (Math.abs(p-lastp)/100f*2000);
        ValueAnimator animator=ValueAnimator.ofFloat(lastp,p).setDuration(d);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float v= (float) animation.getAnimatedValue();
                tv_value.setText(Math.round(v)+"  进度");
            }
        });
        animator.start();
    }

    @Override
    protected void onResume() {
        super.onResume();
//        testView.setShowProGress(77);
        UpdateThread();
    }

    boolean isLoop=true;
    private void UpdateThread() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {

                    while (isLoop){
                        Thread.sleep(3000);
                        Message message=new Message();
                        float p= (float) (Math.random()*100);
                        if ((int)p==88){
                            isLoop=false;
                        }
                        message.obj=p;
                        handler.sendMessage(message);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

以上就是带动画带指针的刻度盘的全部内容,欢迎一起讨论学习。