自定義儀表盤MyDashBoardView 實時修改最值和當前值

愛看美劇真是太好了發表於2018-01-03

1.寫在前面

   專案中做了個儀表盤效果,需要支援在java中實時更改最大值以及當前值,效果圖如下

這裡寫圖片描述
同時我這裡處理了下,讓它可以支援float型別的資料

2.實現

   首先這個自定義view沒有涉及事件分發和動畫,純粹全部都是draw,所以是個練手的好機會。對這個view分析下,可以分為如下幾部分

這裡寫圖片描述
我們需要做的就是按部就班的全部畫出來即可。

2.1 外弧

   我這裡方法可能一別人不一樣,我首先就移動了畫布,將畫布的移動到view的中心,所以中心座標是(0,0),我覺得這樣子後面座標點的座標好算點。

    //整個view的長 寬
    private int mWidth,mHeight;
        @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;

        /**
         * 圓弧外切矩形的寬度,圓弧的半徑即為它的一半
         */
        mRectWidth = (float) (Math.min(mWidth,mHeight)*0.9);

    }
    /**
       * 將圓心移到控制元件中間
       */
      canvas.translate(mWidth / 2, mHeight / 2);
複製程式碼

上方的Math.min(x,y)*0.9是 正方形類自定義view的常規處理,畢竟這種view的長寬設定時一般都是一樣的,但是萬一設定的不一樣,就取最小的同時在縮小點,留點padding

這裡寫圖片描述

第一步當然畫如上圖的弧,我為了更明顯,紅色的座標系是我加上過去的。這個弧比較容易畫,比較明顯弧的起始角度是135,弧掃過的角度是270

    /**
     * 畫圓弧
     * @param canvas
     */
    private void drawArc(Canvas canvas){
        mArcPaint.setStyle(Paint.Style.STROKE);
        RectF rectFArc = new RectF(-mRectWidth / 2, -mRectWidth / 2, mRectWidth / 2, mRectWidth / 2);
        canvas.drawArc(rectFArc,mStartAngle,mSweepAngle,false,mArcPaint);
    }
複製程式碼

一般來說,畫圓弧的第一步是獲取圓弧的外切矩形,比如上的rectFArc

2.2 畫短刻度

   這裡為什麼是先畫短刻度,而不是長刻度,我這裡的處理是一共分為十個大份,每個大份分為十個小份,其實長刻度是屬於短刻度的,屬於包含關係,所以我先講短刻度畫出來,然後長刻度覆蓋即可。畫短刻度的思路是,先找出短刻度兩個點的座標,然後將兩個點這條線畫出來,在通過畫布旋轉

這裡寫圖片描述
如上圖所示,每個短刻度都有兩個點,紅線是一個點的x,y座標,綠線是一個點的x,y座標,將這個短刻度與圓心的連線,所以這兩個點其實就是三角函式的值。設定好短刻度的長度,然後就知道這兩個點到圓心連線的長度,這樣就可以通過三角函式算出兩個點的座標。

    /**
     * 畫短刻度
     * @param canvas
     */
    private void drawGraduation2(Canvas canvas){

        /**
         * 先將畫布儲存,等下畫長度前拿出來,這樣 畫布相當於是最新的,不然 先畫短刻度後 在畫長刻度 畫布的角度已經變了
         */
        canvas.save();
        /**
         * 將角度135 轉成弧度 3pi/4
         */
        float startPi = (float) Math.toRadians(mStartAngle);
        /**
         * 刻度兩個點的座標
         */
        float[] point1 = new float[2];
        float[] point2 = new float[2];

        point1[0] = (float) (Math.cos(startPi)*mRectWidth/2);
        point1[1] = (float) (Math.sin(startPi) * mRectWidth / 2);

        /**
         * 半徑減去短刻度的長度,然後算出三角函式
         */
        point2[0] = (float) (Math.cos(startPi)*(mRectWidth/2-shortLength));
        point2[1] = (float) (Math.sin(startPi) * (mRectWidth / 2 - shortLength));

        for(float i=0;i<=270;i+=avgSmallAngle){
            canvas.drawLine(point2[0],point2[1],point1[0],point1[1],mArcPaint);
            canvas.rotate(avgSmallAngle);
        }
    }
複製程式碼

2.3 畫長刻度

   長刻度如上一樣,只是畫布旋轉的角度不一樣而已,直接看程式碼即可

    /**
     * 畫長刻度
     * @param canvas
     */
    private void drawGraduation(Canvas canvas){
        canvas.restore();
        /**
         * 再次儲存 為了以後的畫布而言,拿出來時 還是最初的
         */
        canvas.save();

        /**
         * 將角度135 轉成弧度 3pi/4
         */
        float startPi = (float) Math.toRadians(mStartAngle);
        /**
         * 刻度兩個點的座標
         */
        float[] point1 = new float[2];
        float[] point2 = new float[2];

        point1[0] = (float) (Math.cos(startPi)*mRectWidth/2);
        point1[1] = (float) (Math.sin(startPi) * mRectWidth / 2);

        point2[0] = (float) (Math.cos(startPi)*(mRectWidth/2-longLength));
        point2[1] = (float) (Math.sin(startPi) * (mRectWidth / 2 - longLength));

        for(float i=0;i<=270;i+=avgLargeAngle){
            canvas.drawLine(point2[0],point2[1],point1[0],point1[1],mArcPaint);
            canvas.rotate(avgLargeAngle);
        }
    }
複製程式碼

這裡寫圖片描述

2.4 畫長刻度讀數

   我們在畫長刻度時,首先要獲取長刻度的內容,這裡程式碼比較簡單,只是我在這裡將資料處理了下,所有的數都保留一位有效數字,這樣如果最大值是小數時,也可以支援,免得畫出來很難看

  /**
     * 長刻度文字內容陣列
     */
    private String[] textContent  = null;
        /**
     * 獲取長刻度 文字內容
     */
    private void getTextContent(){
        mMax  = getmMax();
        textContent = new String[largeSection + 1];
        for(int i=0;i<textContent.length;i++){
            float avgF = (mMax-mMin)/largeSection;
            float result = avgF*i;
            Log.d(TAG,"------result------    "+result);
            /**
             * 對值進行處理,保留一位小數
             */
            String lastResutl = new DecimalFormat("#.0").format(result);
            /**
             * 注意和上面的區別,上面的是 所有的值 始終保留一位小數,下面是 如果是整數則不保留,如果不是整數,則保留一位小數
             */
            //String lastResutl = new DecimalFormat("#.0").format(result);
            textContent[i] = lastResutl;
        }
    }
複製程式碼

這讀數都是隨著圓弧畫出來的,所以這裡使用的方法是drawTextOnPath,其中需要一個path物件,我們需要做的就是給path新增一個圓弧Arc,這裡我們需要畫11個資料,同時圓弧也要畫11次,只不過圓弧每次起點不一樣,同時這裡我對角度處理了下,等下可以對比下

這裡寫圖片描述

    /**
     * 畫長刻度 文字
     * @param canvas
     */
    private void drawText(Canvas canvas){
        canvas.restore();
        canvas.save();
        inArcRect = new RectF(-mRectWidth / 2+longLength+longLength,-mRectWidth / 2+longLength+longLength,mRectWidth / 2-longLength-longLength,mRectWidth / 2-longLength-longLength);
        mInPath = new Path();

        textRect = new Rect();

        mArcPaint.setTextAlign(Paint.Align.LEFT);
        mArcPaint.setTextSize(sp2px(9));
        mArcPaint.setStyle(Paint.Style.FILL);

        for(int i=0;i<textContent.length;i++){
            mArcPaint.getTextBounds(textContent[i],0,textContent[i].length(),textRect);

            /**
             * 處理角度  這裡暫時把文字的寬度當作弧長,然後通過弧長算出對應的角度,
             * 弧長公式  l(弧長) = n(圓心角) × 派 × r(半徑)  / 180
             * 所以反推出  n = l*180/派×r
             */
            int θ = (int) ((textRect.width()/2*180)/(Math.PI*mRectWidth / 2-longLength-longLength));
            mInPath.reset();
            mInPath.addArc(inArcRect, mStartAngle + i * (mSweepAngle / largeSection)-θ, mSweepAngle);
            canvas.drawTextOnPath(textContent[i],mInPath,0,0,mArcPaint);
        }

    }
複製程式碼

不處理是這樣的

這裡寫圖片描述

處理的方法程式碼中寫的很詳細了,就是將文字的長度近似於弧長,然後反推弧長公式算出角度,再改變起始角度即可,上面 0 前面那個 . 我已經處理了,寫部落格時沒發現。

2.5 畫表頭

   畫表頭比較簡單,一般來說這種只需設定一次的,設定到xml中比較方便。所以在xml自定義一個表頭屬性即可。然後在建構函式中獲取表頭內容,如果為空則不畫,不為空 則畫出來,畫文字這裡因為畫布在中心的原因比較簡單,x座標為0,y座標取半徑的一般既可,比較簡單直接上程式碼了

    /**
     * 畫表頭,如果表頭不存在即不畫
     * @param canvas
     */
    private void drawHeadText(Canvas canvas){
        if(!TextUtils.isEmpty(mHeadText)){
            canvas.restore();
            canvas.save();

            mArcPaint.setTextSize(sp2px(14));
            mArcPaint.setTextAlign(Paint.Align.CENTER);
            //mArcPaint.getTextBounds(mHeadText,0,mHeadText.length(),textRect);
            canvas.drawText(mHeadText,0,-mRectWidth/4,mArcPaint);
        }
    }
複製程式碼

2.6 畫指標

   這裡我畫指標的思路是先算出指標的角度,然後找出指標的四個點座標,通過path畫出來即可,所以指標的難點是找出四個點的座標。我這裡畫了個草圖,將指標四個點排下序。

這裡寫圖片描述
如上圖所示,外弧是最外面的弧,內弧是文字的弧,這四個點中的A點不用算了,就是(0,0),C點其實也比較簡單就是AC長度的三角函式,B 的座標其實就是AB長度的三角函式,這裡我將指標由兩部分組成,一個是ABD的等邊三角形和BCD的三角形,所以AC和AB之間的角度是30,這樣就可以利用三角函式了(AB是起始角度+30,AD是起始角度-30

    /**
     * 畫指標,因為這裡並不是將 指標畫出來 然後算出角度 讓它旋轉,這裡是直接算出角度 然後畫出來,所以我們畫指標 首先就是算出指標角度
     */
    private void drawPointer(Canvas canvas){
        canvas.restore();
        canvas.save();

        float [] pointC = new float[2];
        float[] pointB = new float[2];
        float[] pointD = new float[2];

        mArcPaint.setStyle(Paint.Style.FILL);
        /**
         * 算出指標的弧度
         */
        float angle = (float) Math.toRadians((mCurrentValue/mMax)*mSweepAngle+mStartAngle);
        Log.d(TAG, "   ------------angle-----------    " + angle);

        /**
         * 指標的半徑
         */
        float pointerRadius = mRectWidth/2-longLength-longLength-6;
        mInPath.reset();
        float d = 36;

        /**
         * C點座標  指標最末點的位置座標(遠離圓心)
         */
        pointC[0] = (float) (Math.cos(angle)*pointerRadius);
        pointC[1] = (float) (Math.sin(angle) * pointerRadius);

        /**
         * B點座標
         */
        pointB[0] = (float) (Math.cos(angle+Math.toRadians(30))*d);
        pointB[1] = (float) (Math.sin(angle + Math.toRadians(30)) * d);

        /**
         * D點座標
         */
        pointD[0] = (float) (Math.cos(angle - Math.toRadians(30)) * d);
        pointD[1] = (float) (Math.sin(angle - Math.toRadians(30)) * d);

        mInPath.lineTo(pointB[0], pointB[1]);
        mInPath.lineTo(pointC[0], pointC[1]);
        mInPath.lineTo(pointD[0], pointD[1]);
        mInPath.close();
        canvas.drawPath(mInPath,mArcPaint);
    }
複製程式碼

這裡寫圖片描述

   實時讀數和表頭是一樣的,就不上圖和程式碼了。 到這裡為止,剩下的就沒什麼事了,測量和寫公開方法,讓外部呼叫,這裡比較套路,就不上程式碼了,等下看原始碼即可。

3.留步

原始碼地址,如果幫到你給個star 如何 ^_^!

相關文章