Android 自定義儀表盤

最近需要寫一個用到各種圖表的項目,比較過後決定用hellocharts框架,感覺足夠簡潔,後來發現這框架裏沒有儀表盤這個控件,但又不想換其他框架,於是在網上搜索一番,找到一個儀表盤學習demo,嘗試後初步改成了所需樣式。界面如下:

這裏寫圖片描述

因爲爲demo,界面比較醜陋,需要再美化。另代碼設計也有很大改進精簡空間,待正式項目中再進行修改,此爲學習所用。

所參考文章:http://blog.csdn.net/qq_26411333/article/details/52399831
原文是可以觸摸改變指針指向,因需求不同,將此功能刪除了。

改動後的view不同的是可以設置初始值和終點值,可以設置需要的刻度段數,可以通過設置改變指針指向,當然所有可變量都可抽出設置方法,可跟需求改動。
代碼改動過程中,有若干點需要注意:
1、在分多種顏色繪製時,總是出現最後一個顏色覆蓋掉之前不同的顏色,查詢才知道
canvas.drawPath(linePath, linePaint)中path是包含從繪製起所有路徑,所以在最後改變時,也會將之前所有路徑改爲同一風格,換成其他draw方法即可。
2、繪製過程中,會用到一些三角函數公式,此座標是以指針起點固定點爲原點,水平向右爲x軸正方向,豎直向下爲y軸正方向,所以第一二三四象限是順時針定義的。因爲外圓內院半徑固定,可由任何角度根據三角函數公式獲取所需值。PathMeasure等類也有所封裝。

全部代碼:

package xr.hellochartsdemo.ui.activity.dashboard;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.RectF;
import android.renderscript.Sampler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

import java.math.BigDecimal;

import static android.R.attr.x;
import static android.R.attr.y;

/**
 * Created by wxy .
 * 儀表盤
 */
public class DashboardView extends View {

    private int width;
    private int height;

    private Paint outerCirclePaint;//外層圓的畫筆
    private Paint outerCirclePaint2;//外層圓的畫筆2
    private Paint outerCirclePaint3;//外層圓的畫筆3
    private Paint innerCirclePaint;//內層圓的畫筆
    private Paint linePaint;//線段畫筆
    private Paint arrowPaint;//指針畫筆
    private Paint textPaint;//標註文字
    private Paint textPaint2;//目標指針文字

    private Path outerCirclePath;//外層圓的Path
    private Path innerCirclePath;//內層圓的Path
    private Path linePath;//線段的Path
    private Path arrowPath;//指針的Path
    private Path measureArrowPath;//arrowPath藉助該Path來保持一定的長度

    private RectF outRectF;//用於繪製外層圓   通過四個座標參數來確定一個矩形的區域。
    private RectF innerRectF;//用於繪製內層圓

    private int count = 10;//畫count根線
    private static int outerR = 100;//外部圓環的半徑
    private static int innerR = (int) (outerR * 0.9f);//內部圓環的半徑
    private int shortageAngle = 60;//缺失的部分的角度
    private int startAngle;//開始的角度
    private int sweepAngle;//掃過的角度
    private int endAngle;

    private float[] leftEndPoint;//左側邊界的座標
    private float[] rightEndPoint;//右側邊界的座標
    private float leftEndTan;//左側邊界的tan值
    private float rightEndTan;//右側邊界的tan值

    private float nowX = 0;//觸摸位置的橫座標
    private float nowY = 0;//觸摸位置的縱座標
    private static float percent = 0.9f;//指針與內層圓的比值
    private float arrowLength = innerR * percent;//指針的長度

    double aimSweepAngel = 0;//起始角度 起點
    float startValue = 0;//默認起始值
    float endValue = 100;//默認終止值
    private int textCount = count;//標註文字個數 默認

    private PathMeasure arrowMeasure;//用於指針的測量
    private boolean isColorful = true;
    private double aimValue = startValue;


    public DashboardView(Context context) {
        super(context);
        initPaint();
        initAngle();
    }

    public DashboardView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initPaint();
        initAngle();
    }

    public DashboardView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initPaint();
        initAngle();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        width = w;
        height = h;
        //讓指針一開始指向正上方
        nowX = 0;
        nowY = -1;
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(width / 2, height / 2);
        drawOuterCircle(canvas);
        drawInnerCircle();
        drawLine(canvas);
        drawArrow(canvas);
        drawText(canvas);
        drawAimText(canvas);
    }

    /**
     * 外層圓圈
     */
    private void drawOuterCircle(Canvas canvas) {
        //一般繪製圓圈的方法,不做介紹了
        outerCirclePath = new Path();
        if (outRectF == null) {
            outRectF = new RectF(-outerR, -outerR, outerR, outerR);
        }
        outerCirclePath.addArc(outRectF, startAngle, sweepAngle);
        canvas.drawArc(outRectF, startAngle, sweepAngle, false, outerCirclePaint);
        if (isColorful) {//是否要錶盤是彩色的
            canvas.drawArc(outRectF, startAngle, sweepAngle / 5, false, outerCirclePaint);
            canvas.drawArc(outRectF, startAngle + sweepAngle / 5, 3 * sweepAngle / 5, false, outerCirclePaint2);
            canvas.drawArc(outRectF, startAngle + sweepAngle / 5 + 3 * sweepAngle / 5, sweepAngle / 5, false, outerCirclePaint3);
        }
    }

    /**
     * 內層圓圈
     */
    private void drawInnerCircle() {
        //一般繪製圓圈的方法,不做介紹了
        innerCirclePath = new Path();
        if (innerRectF == null) {
            innerRectF = new RectF(-innerR, -innerR, innerR, innerR);
        }
        innerCirclePath.addArc(innerRectF, startAngle, sweepAngle);
    }

    /**
     * 畫直線,組成一個類似於弧形的形狀
     *
     * @param canvas
     */
    private void drawLine(Canvas canvas) {
        linePath = new Path();
        //用於外層圓的測量
        //PathMeasure是一個用來測量Path的類
        //創建 PathMeasure 並關聯一個指定的Path(Path需要已經創建完成)。
        PathMeasure outMeasure = new PathMeasure(outerCirclePath, false);
        float outlength = outMeasure.getLength();
        float[] outPos = new float[2];

        //用於內層圓的測量
        PathMeasure inMeasure = new PathMeasure(innerCirclePath, false);
        float inlength = inMeasure.getLength();
        float[] inPos = new float[2];

        //確定左側末尾的座標以及tan值
        if (leftEndPoint == null) {
            leftEndPoint = new float[2];
            //通過getPosTan拿到內層圓的左側末尾座標
            inMeasure.getPosTan(0, leftEndPoint, null);
            //因爲指針要短一點;所以x,y都乘以percent纔是指針真正的左側末尾座標
            leftEndPoint[0] = leftEndPoint[0] * percent;
            leftEndPoint[1] = leftEndPoint[1] * percent;
            //確定指針在左側末尾時的tan值
            leftEndTan = leftEndPoint[1] / leftEndPoint[0];
        }

        //確定右側末尾的座標以及tan值
        if (rightEndPoint == null) {
            rightEndPoint = new float[2];
            //通過getPosTan拿到內層圓的右側末尾座標
            inMeasure.getPosTan(inlength, rightEndPoint, null);
            //因爲指針要短一點;所以x,y都乘以percent纔是指針真正的右側末尾座標
            rightEndPoint[0] = rightEndPoint[0] * percent;
            rightEndPoint[1] = rightEndPoint[1] * percent;
            //確定指針在右側末尾時的tan值
            rightEndTan = rightEndPoint[1] / rightEndPoint[0];
        }

        //用來畫多條線段,組成弧形
        for (int i = 0; i <= count; i++) {
            //外層圓當前的弧長
            float outNowLength = outlength * i / (count * 1.0f);
            //當前弧長下對應的座標outPos
            outMeasure.getPosTan(outNowLength, outPos, null);

            //內層圓當前的弧長
            float inNowLength = inlength * i / (count * 1.0f);
            //當前弧長下對應的座標inPos
            inMeasure.getPosTan(inNowLength, inPos, null);

            //moveTo到內層圓弧上的點
            linePath.moveTo(outPos[0], outPos[1]);
            //lineTo到外層圓弧上的點
            linePath.lineTo(inPos[0], inPos[1]);

            if (isColorful) {
                if (i <= count / 5) {
                    linePaint.setColor(Color.GREEN);
                } else if (i >= count - count / 5) {
                    linePaint.setColor(Color.RED);
                } else {
                    linePaint.setColor(Color.BLUE);
                }
            }
            //問題:最後的顏色 會覆蓋掉之前的顏色??
            //分析:在此循環中 linePath一直在添加直線路徑 當i=9時 已經包含所有路徑
            //證明:把canvas.drawPath(linePath, linePaint); 放循環外 亦可繪製全部小刻標
            //結論:所以此方法只要求得outPos、inPos座標即可 不必此方法
            //Path主要用於繪製複雜的圖形輪廓,比如折線,圓弧以及各種複雜圖案
//          canvas.drawPath(linePath, linePaint);
            canvas.drawLine(outPos[0], outPos[1], inPos[0], inPos[1], linePaint);
        }
//      canvas.drawPath(linePath, linePaint);
    }

    /**
     * 繪製指針
     *
     * @param canvas
     */
    private void drawArrow(Canvas canvas) {

//      double f = Math.toRadians(aimSweepAngel + shortageAngle / 2 - 90);
        double f = Math.toRadians(aimSweepAngel + startAngle);
        nowX = (float) Math.cos(f) * arrowLength;
        nowY = (float) Math.sin(f) * arrowLength;

        arrowPath = new Path();
        arrowPath.reset();
        //這時,指針的末尾位置最終確定了,可以繪製了
        arrowPath.moveTo(0, 0);
        arrowPath.lineTo(nowX, nowY);
        canvas.drawPath(arrowPath, arrowPaint);
    }
    /**
     * 顯示指針指向的值
     * @param canvas
     */
    private void drawAimText(Canvas canvas) {
        //中心點下
        canvas.drawText(formatDouble(aimValue)+ "", -10 , outerR , textPaint2);
    }

    /**
     * 設置儀表刻度個數
     * @param n
     */
    public void setTextCount(int n) {
        textCount = n;
    }

    /**
     * 顯示儀表刻度值
     * @param canvas
     */
    public void drawText(Canvas canvas) {
        float angle = startAngle;
        double value = startValue;
        for (int i = 0; i <= textCount; i++) {
            angle = startAngle + i * sweepAngle / textCount;
            value = startValue + i * formatFloat((endValue - startValue) / textCount);

            if (isColorful) {
                if (i <= textCount / 5) {
                    textPaint.setColor(Color.GREEN);
                } else if (i >= textCount - textCount / 5) {
                    textPaint.setColor(Color.RED);
                } else {
                    textPaint.setColor(Color.BLUE);
                }
            }
            drawtext(canvas, angle, value + "");
        }
    }

    //float處理
    private float formatFloat(float f) {
        BigDecimal bigDecimal = new BigDecimal(f);
        return bigDecimal.setScale(2, BigDecimal.ROUND_HALF_UP).floatValue();
    }
    private double formatDouble(double d) {
        BigDecimal bigDecimal = new BigDecimal(d);
        return bigDecimal.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
    }

    private void drawtext(Canvas canvas, double angle, String value) {
        double f = Math.toRadians(angle);
        float x1 = (float) Math.cos(f) * (innerR - 5);
        if (x1 > 0) {
            x1 -= 26;
        } else if (x1 == 0) {
            x1 -= 10;
        }
        float y1 = (float) Math.sin(f) * (innerR - 5);
        canvas.drawText(value + "", x1, y1, textPaint);
    }

    public void setArrowData(double f) {//根據值轉換成角度 再轉換成座標
        aimValue = f;
        double percent = (aimValue - startValue) / (endValue - startValue);
        double angel = percent * sweepAngle;
        aimSweepAngel = angel; //0 -> sweepAngle
    }

    public void setStartingValue(float startingValue) {
        this.startValue = startingValue;
    }

    public void setEndValue(float endValue) {
        this.endValue = endValue;
    }

    /**
     * 初始化畫筆
     */
    private void initPaint() {
        if (outerCirclePaint == null) {
            outerCirclePaint = new Paint();
            outerCirclePaint.setStyle(Paint.Style.STROKE);
            outerCirclePaint.setStrokeWidth(4);
            outerCirclePaint.setColor(Color.GREEN);
            outerCirclePaint.setAntiAlias(true);//抗鋸齒
        }
        if (outerCirclePaint2 == null) {
            outerCirclePaint2 = new Paint();
            outerCirclePaint2.setStyle(Paint.Style.STROKE);
            outerCirclePaint2.setStrokeWidth(4);
            outerCirclePaint2.setColor(Color.BLUE);//blue
            outerCirclePaint2.setAntiAlias(true);//抗鋸齒
        }
        if (outerCirclePaint3 == null) {
            outerCirclePaint3 = new Paint();
            outerCirclePaint3.setStyle(Paint.Style.STROKE);
            outerCirclePaint3.setStrokeWidth(4);
            outerCirclePaint3.setColor(Color.RED);//
            outerCirclePaint3.setAntiAlias(true);//抗鋸齒
        }
        if (innerCirclePaint == null) {
            innerCirclePaint = new Paint();
            innerCirclePaint.setStyle(Paint.Style.STROKE);
//          outerCirclePaint.setColor(Color.BLACK);
            innerCirclePaint.setColor(Color.BLUE);
            innerCirclePaint.setAntiAlias(true);//抗鋸齒
        }
        if (linePaint == null) {
            linePaint = new Paint();
            linePaint.setStyle(Paint.Style.STROKE);
            linePaint.setStrokeWidth(2);
            linePaint.setColor(0xff1d8ffe);
            linePaint.setAntiAlias(true);
        }
        if (arrowPaint == null) {
            arrowPaint = new Paint();
            arrowPaint.setStyle(Paint.Style.FILL_AND_STROKE);
            arrowPaint.setColor(Color.BLUE);
            arrowPaint.setStrokeWidth(2);
            arrowPaint.setAntiAlias(true);
        }
        if (textPaint == null) {
            textPaint = new Paint();
            textPaint.setStyle(Paint.Style.STROKE);
            textPaint.setColor(Color.BLACK);
            textPaint.setTextSize(12);
            textPaint.setStrokeWidth(1);
            textPaint.setAntiAlias(true);
        }
        if (textPaint2 == null) {
            textPaint2 = new Paint();
            textPaint2.setStyle(Paint.Style.STROKE);
            textPaint2.setColor(Color.CYAN);
            textPaint2.setTextSize(18);
            textPaint2.setStrokeWidth(1);
            textPaint2.setAntiAlias(true);
        }
    }

    /**
     * 根據shortageAngle來調整圓弧的角度
     */
    private void initAngle() {
        sweepAngle = 360 - shortageAngle;
        startAngle = 90 + shortageAngle / 2;
        endAngle = 90 - shortageAngle / 2;
    }
}

調用代碼:

dashboardView = (DashboardView) findViewById(R.id.dashboardView);
        btn_next = (Button) findViewById(R.id.btn_next);
        dashboardView.setStartingValue(0);
        dashboardView.setEndValue(10);
        dashboardView.setTextCount(5);
        dashboardView.invalidate();
        btn_next.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //隨機
                new Handler().post(new Runnable() {
                    @Override
                    public void run() {
                        d = Math.random() * 10;
                        dashboardView.setArrowData(d);
                        dashboardView.invalidate();
                    }
                });
            }

        });
發佈了40 篇原創文章 · 獲贊 7 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章