public class CarSpeedDashboardSurfaceView extends SurfaceView implements SurfaceHolder.Callback.Runnable {
    SurfaceHolder surfaceHolder;
    private boolean isDrawing;

    private Paint mPaintOutDashboard;// External code table arc brush
    private Paint mPaintSpeedPoint;// The pen for the pointer to the code table
    private Paint mPaintSpeedScale;// add a pencil to the table
    private Paint mPaintSpeedScaleText;// write text on the scale
    private Paint mPaintSpeedText;// Real-time codetable corresponds to numeric brush
    private int outDashBoardWidth = 40;// The width of the arc of the external codetable


    private float rotateValue = 50;// Animation needs to change the arc Angle
    private float DEFAULT_START_ANGLE = 50;// The default rotation Angle is (360-260)/2 when the canvas is moved to the center

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

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

    public CarSpeedDashboardSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        surfaceHolder = getHolder();
        surfaceHolder.addCallback(this);
        setKeepScreenOn(true);
// setBackgroundColor(Color.TRANSPARENT);
// setZOrderOnTop(true);
// surfaceHolder.setFormat(PixelFormat.TRANSLUCENT);
        init();
    }
    /** * Initialization parameters */
    private void init(a) {
        mPaintOutDashboard = new Paint();
        mPaintOutDashboard.setStrokeWidth(outDashBoardWidth);
        mPaintOutDashboard.setStyle(Paint.Style.STROKE);
        mPaintOutDashboard.setTextAlign(Paint.Align.CENTER);
        mPaintOutDashboard.setAntiAlias(true);
        mPaintOutDashboard.setStrokeCap(Paint.Cap.ROUND);


        mPaintSpeedPoint = new Paint();
        mPaintSpeedPoint.setColor(Color.BLACK);
        mPaintSpeedPoint.setStrokeWidth(20);
        mPaintSpeedPoint.setAntiAlias(true);
        mPaintSpeedPoint.setStyle(Paint.Style.STROKE);
        mPaintSpeedPoint.setTextAlign(Paint.Align.CENTER);
        mPaintSpeedPoint.setStrokeCap(Paint.Cap.ROUND);


        mPaintSpeedScale = new Paint();
        mPaintSpeedScale.setColor(Color.BLUE);
        mPaintSpeedScale.setStrokeWidth(5);
        mPaintSpeedScale.setAntiAlias(true);
        mPaintSpeedScale.setStyle(Paint.Style.STROKE);

        mPaintSpeedScaleText = new Paint();
        mPaintSpeedScaleText.setColor(Color.BLACK);
        mPaintSpeedScaleText.setStrokeWidth(3);
        mPaintSpeedScaleText.setTextSize(26);
        ;
        mPaintSpeedScaleText.setAntiAlias(true);

        mPaintSpeedText = new Paint();
        mPaintSpeedText.setColor(Color.BLACK);
        mPaintSpeedText.setTextSize(48);
        ;
        mPaintSpeedText.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
        mPaintSpeedText.setAntiAlias(true);
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = measureSpec(widthMeasureSpec);
        int height = measureSpec(heightMeasureSpec);
        setMeasuredDimension(Math.min(width,height), Math.min(width,height));

        // The gradient of width and height information can be obtained after the measurement is completed
        LinearGradient sweepGradient = new LinearGradient(0
                , getMeasuredHeight(), getMeasuredWidth()
                , getMeasuredHeight()
                , Color.BLUE, Color.RED, Shader.TileMode.CLAMP);
        mPaintOutDashboard.setShader(sweepGradient);

    }

    private int measureSpec(int measureSpec) {
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        int defaultWidth = 400;
        switch (specMode) {

            case MeasureSpec.EXACTLY:

                defaultWidth = specSize;
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.UNSPECIFIED:
                defaultWidth = 400;
                break;
        }
        return defaultWidth;
    }
    @Override
    public void surfaceCreated(@NonNull SurfaceHolder holder) {
        isDrawing = true;
        new Thread(this).start();
    }

    @Override
    public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {}@Override
    public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
        isDrawing = false;
    }

    @Override
    public void run(a) {
        while (isDrawing) {
            Canvas canvas = null;
            try {
                canvas = surfaceHolder.lockCanvas();
                 if(canvas! =null) { canvas.drawColor(Color.WHITE); drawDashBoard(canvas); }}finally {
                if(canvas ! =null) { surfaceHolder.unlockCanvasAndPost(canvas); }}}}private void drawDashBoard(Canvas canvas) {
        // Draw the outer ring
        canvas.drawArc(outDashBoardWidth / 2, outDashBoardWidth / 2, Math.min(getMeasuredWidth(), getMeasuredHeight()) - outDashBoardWidth / 2, Math.min(getMeasuredWidth(), getMeasuredHeight()) - outDashBoardWidth / 2.140.260.false, mPaintOutDashboard);

        drawSpeedScale(canvas);

        // Draw the progress display in the progress bar
        canvas.drawArc(outDashBoardWidth / 2, outDashBoardWidth / 2, Math.min(getMeasuredWidth(), getMeasuredHeight()) - outDashBoardWidth / 2, Math.min(getMeasuredWidth(), getMeasuredHeight()) - outDashBoardWidth / 2.140, rotateValue - DEFAULT_START_ANGLE, false, mPaintSpeedPoint);

        drawSpeedPoint(canvas);
        drawCenterSpeedSize(canvas);

    }


    /** * draws the scale and its corresponding code value **@param canvas
     */
    private void drawSpeedScale(Canvas canvas) {
        canvas.save();
        float sweepAngle = 260;
        float a = sweepAngle / 26;
        canvas.translate(Math.min(getMeasuredWidth(), getMeasuredHeight()) / 2, Math.min(getMeasuredWidth(), getMeasuredHeight()) / 2);

        canvas.rotate(DEFAULT_START_ANGLE);

        for (int i = 0; i <= 26; i++) {
            if (i == 0 || i == 26 || i % 5= =0) {
                canvas.drawLine(0, Math.min(getMeasuredWidth(), getMeasuredHeight()) / 2 - outDashBoardWidth, 0, Math.min(getMeasuredWidth(), getMeasuredHeight()) / 2 - 80, mPaintSpeedScale);
            } else {
                canvas.drawLine(0, Math.min(getMeasuredWidth(), getMeasuredHeight()) / 2 - outDashBoardWidth, 0, Math.min(getMeasuredWidth(), getMeasuredHeight()) / 2 - 60, mPaintSpeedScale);
            }
            String text = i * 10 + "";


            Rect rect = new Rect();
            String msg = "999";
            mPaintSpeedScaleText.getTextBounds(msg, 0, msg.length(), rect);
// canvas.drawText(text, 0, text.length(),0, Math.min(getMeasuredWidth(), getMeasuredHeight()) / 2 - 85, mPaintSpeedScaleText);
// canvas.rotate(a);


            canvas.rotate(-90);
            canvas.translate(-1 * (Math.min(getMeasuredWidth(), getMeasuredHeight()) / 2 - outDashBoardWidth - 60-rect.width()/2), 0);
            canvas.rotate(90 - DEFAULT_START_ANGLE - a * i);
            canvas.drawText(text, 0, text.length(), 0.0, mPaintSpeedScaleText);
            canvas.rotate(-1 * (90 - DEFAULT_START_ANGLE - a * i));
            canvas.translate(Math.min(getMeasuredWidth(), getMeasuredHeight()) / 2 - outDashBoardWidth - 60-rect.width()/2.0);
            canvas.rotate(90);

            canvas.rotate(a);
//
        }
        canvas.restore();

    }

    /** * draws a pointer to the codetable **@param canvas
     */
    private void drawSpeedPoint(Canvas canvas) {
        canvas.drawPoint(Math.min(getMeasuredWidth(), getMeasuredHeight()) / 2, Math.min(getMeasuredWidth(), getMeasuredHeight()) / 2, mPaintSpeedPoint);

        canvas.save();
        canvas.translate(Math.min(getMeasuredWidth(), getMeasuredHeight()) / 2, Math.min(getMeasuredWidth(), getMeasuredHeight()) / 2);
        canvas.rotate(rotateValue);
        canvas.drawLine(0, Math.min(getMeasuredWidth(), getMeasuredHeight()) / 2 - outDashBoardWidth * 4.0.0, mPaintSpeedPoint);
        canvas.restore();

    }

    /** * draws the corresponding codetable value **@param canvas
     */
    private void drawCenterSpeedSize(Canvas canvas) {
        String value = Math.round(rotateValue - 50) + "";
        Rect rect = new Rect();
        mPaintSpeedText.getTextBounds(value, 0, value.length(), rect);

        canvas.drawText(value, 0, value.length(), Math.min(getMeasuredWidth(), getMeasuredHeight()) / 2 - rect.width() / 2
                , Math.min(getMeasuredWidth(), getMeasuredHeight()) / 2 - 40, mPaintSpeedText);

    }


    // Dynamic change
    public void setValue(float value){
      this.rotateValue=310-value; }}Copy the code