自定義view————碼錶

madreain發表於2017-08-09

真的玩起了自定義view,就一直想造點什麼自定義view來練練手,今天就碼錶來給大家講解一下碼錶的撰寫過程。

效果圖
效果圖

效果圖看了,廢話就不多說了,直接來上真傢伙。分享過程:碼錶的底層背景;碼錶顯示的當前進度;當前速度值顯示;速度刻度值;對當前進度進行漸變處理

碼錶的底層背景

效果圖可以看出,首先畫給黑色的背景,這裡用drawArc()來畫,算出具體的RectF及其開始角度和結束角度

   //開始的角度
    private float mStart = -210;
    //總的角度
    private static final int FULL_ANGLE = 240;
    //進度條的寬度
    private float progressWidth = dipToPx(8);

    //畫底盤
    RectF mBigOval = new RectF(progressWidth / 2, progressWidth / 2,
                 getWidth() - (progressWidth + 1) / 2, getHeight() - (progressWidth + 1) / 2);
    canvas.drawArc(mBigOval, mStart, FULL_ANGLE, false, mBackgroundPaint); //底色的弧複製程式碼

mBackgroundPaint的相關設定可以去設定你自己想要的效果

碼錶的當前進度

碼錶的底層背景說完了,接下來就來介紹一下碼錶的時時刻刻變化的當前進度,其實和碼錶的底層背景的繪製是一樣的,只不過就是角度時變化的

首先算出當前速度佔這個碼錶的角度是多少,我這裡的最大角度是240,最高速度是300,當時速度佔的總角度=當前速度/300*240

/**
     * 計算當前速度佔最高速度的多少
     * 240是角度
     *
     * @param delta
     */
    public void setDelta(float delta) {
        this.mspeed = delta;
        this.delta = delta * 240 / 300;
        invalidate();
    }複製程式碼

角度算好了就是畫了

canvas.drawArc(mBigOval, mStart, delta, false, mProgressPaint); // 速度的弧複製程式碼

到這一步為止,碼錶就能時時的根據速度來動態變化了,就下來就是來給這個碼錶加些花了

當前速度值顯示

速度值動態顯示用drawText()就能顯示,但是這樣的設定,位置不是居中,因此我們需要計算出當前速度值文字顯示的寬高,然後計算準確的位置來達到好的展示效果

//獲取文字的寬度及其高度
        Rect rect = new Rect();
        String speed = mspeed + "Km/h";
        mtxtPaint.getTextBounds(speed, 0, speed.length(), rect);
        textheight = rect.height();
        textwidth = rect.width();
        //速度顯示
        canvas.drawText(speed, mwidth / 2 - textwidth / 2, mheight / 2 - textheight / 2, mtxtPaint);複製程式碼

到了這一步,去看看車子裡的儀表盤差了速度刻度值,不行,這個花也得加上去

速度刻度值顯示

我這裡的最大速度是300,我用50為一個大節點,共分為0、50、100、150、200、250、300,0-50之間有小刻度值,畫線drawLine(),畫刻度值drawText(),再結合rotate()來達到心中的效果

canvas.translate(mwidth / 2, mheight / 2);
        //循轉60
        canvas.rotate(60);
//        //畫速度刻度值盤
        int count = 45;
        for (int i = 0; i < count; i++) {
            if (i <= 30) {
                if (i % 5 == 0) {
                    canvas.drawLine(0, radius + 10, 0, radius + 60, tmpPaint);
//                    canvas.drawLine(0, radius, 0, radius + 30, tmpPaint);
                    //設定位元速率
                    Rect recttxt = new Rect();
                    String value = String.valueOf(i / 5 * 50);
                    tmpPaint.getTextBounds(value, 0, value.length(), recttxt);
                    valueheight = recttxt.height();
                    valuewidth = recttxt.width();
                    //文字旋轉180
                    canvas.save();
                    canvas.rotate(180, 0, radius);
//                    canvas.drawText(value, -4f, radius + 25f, tmpPaint);
                    canvas.drawText(value, -valuewidth / 2, radius + 25f - valueheight / 2, tmpPaint);
                    canvas.restore();
                } else {
                    canvas.drawLine(0, radius + 40, 0, radius + 60, tmpPaint);
//                    canvas.drawLine(0, radius + 10, 0, radius + 30, tmpPaint);
                }
                canvas.rotate(360 / count, 0f, 0f); //旋轉畫紙
            }
        }複製程式碼

速度刻度值就出來了,需要注意

  • canvas.rotate(60)是為了保證0刻度是和進度是從同一個位置開始的;
  • canvas.rotate(360 / count, 0f, 0f); //旋轉畫紙保證刻度線的位置;
  • 文字寬高的計算上面已經提到過了
  • canvas.rotate(180, 0, radius);是為了保證文字不是倒立的,需另外注意canvas.save();,canvas.restore();保持和恢復方法

大致完整的功能是已經出來了,接下來就是給進度條的變化加一個大大的彩花

當前進度漸變處理

進度漸變處理,對mProgressPaint(當前進度條的畫筆)進行屬性的設定達到彩色漸變效果,setShader()方法結合SweepGradient


   //下面是給漸變顏色做準備的
        //中心點x、y
        centerX = mwidth / 2;
        centerY = mheight / 2;
        sweepGradient = new SweepGradient(centerX, centerY, colors, null);
        rotateMatrix = new Matrix();
        //這個方法很重要
        rotateMatrix.setRotate(118, centerX, centerY);
        sweepGradient.setLocalMatrix(rotateMatrix);
        mProgressPaint.setShader(sweepGradient);複製程式碼

完整程式碼

package com.speedtableview;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.SweepGradient;
import android.util.AttributeSet;
import android.view.View;

/**
 * Created by wujun on 2017/8/9.
 * 碼錶
 * 最高速度300
 *
 * @author madreain
 * @desc
 */

public class SpeedTableView extends View {

    //進度
    private Paint mProgressPaint;
    //背景
    private Paint mBackgroundPaint;
    //速度
    private Paint mtxtPaint;
    //速率刻度
    private Paint tmpPaint;
    //進度條的寬度
    private float progressWidth = dipToPx(8);
    //開始的角度
    private float mStart = -210;
    //總的角度
    private static final int FULL_ANGLE = 240;
    //當前速度佔的角度值
    private float delta = 0;
    //速度
    private float mspeed = 0;
    //半徑
    float radius;
    //寬高
    int mwidth, mheight;
    //速度值文字顯示的寬高
    int textheight;
    int textwidth;
    //速度刻度值文字的寬高
    int valueheight;
    int valuewidth;
    //漸變的顏色
    private int[] colors = new int[]{Color.GREEN, Color.YELLOW, Color.RED, Color.RED};
    //    private PaintFlagsDrawFilter mDrawFilter;
    //掃描/梯度渲染
    private SweepGradient sweepGradient;
    //
    private Matrix rotateMatrix;
    //中心點x,y
    float centerX, centerY;


    public SpeedTableView(Context context) {
        super(context);
        init();
    }

    public SpeedTableView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public SpeedTableView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //寬高
        mwidth = w;
        mheight = h;
        //半徑
        radius = (float) (Math.min(mwidth, mheight) / 2 * 0.8);
        //下面是給漸變顏色做準備的
        //中心點x、y
        centerX = mwidth / 2;
        centerY = mheight / 2;
        sweepGradient = new SweepGradient(centerX, centerY, colors, null);
        rotateMatrix = new Matrix();
        //這個方法很重要
        rotateMatrix.setRotate(118, centerX, centerY);
        sweepGradient.setLocalMatrix(rotateMatrix);
        mProgressPaint.setShader(sweepGradient);
    }

    private void init() {
        //背景
        mBackgroundPaint = new Paint();
        mBackgroundPaint.setAntiAlias(true); //消除鋸齒
        mBackgroundPaint.setStyle(Paint.Style.STROKE); //繪製空心圓
        mBackgroundPaint.setStrokeWidth(progressWidth); //設定進度條寬度
        mBackgroundPaint.setColor(Color.rgb(31, 34, 34)); //設定進度條顏色
        mBackgroundPaint.setStrokeJoin(Paint.Join.ROUND);
        mBackgroundPaint.setStrokeCap(Paint.Cap.ROUND); //設定圓角
        //進度
        mProgressPaint = new Paint();
        mProgressPaint.setAntiAlias(true); //消除鋸齒
        mProgressPaint.setStyle(Paint.Style.STROKE); //繪製空心圓
        mProgressPaint.setStrokeWidth(progressWidth); //設定進度條寬度
        mProgressPaint.setColor(Color.rgb(200, 200, 200)); //設定進度條顏色
        mProgressPaint.setStrokeJoin(Paint.Join.ROUND);
        mProgressPaint.setStrokeCap(Paint.Cap.ROUND); //設定圓角
        //速度值
        mtxtPaint = new Paint();
        mtxtPaint.setAntiAlias(true);
        mtxtPaint.setStrokeJoin(Paint.Join.ROUND);
        mtxtPaint.setStrokeCap(Paint.Cap.ROUND); //設定圓角
        mtxtPaint.setTextSize(100);
        //速度刻度值
        tmpPaint = new Paint(mtxtPaint); //小刻度畫筆物件
        tmpPaint.setStrokeWidth(2);
        tmpPaint.setTextSize(30);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        int sideLength = Math.min(widthSpecSize, heightSpecSize);
        setMeasuredDimension(sideLength, sideLength);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //畫底盤及其速度盤
        RectF mBigOval = new RectF(progressWidth / 2, progressWidth / 2,
                getWidth() - (progressWidth + 1) / 2, getHeight() - (progressWidth + 1) / 2);
        canvas.drawArc(mBigOval, mStart, FULL_ANGLE, false, mBackgroundPaint); //底色的弧
        canvas.drawArc(mBigOval, mStart, delta, false, mProgressPaint); // 速度的弧

        //獲取文字的寬度及其高度
        Rect rect = new Rect();
        String speed = mspeed + "Km/h";
        mtxtPaint.getTextBounds(speed, 0, speed.length(), rect);
        textheight = rect.height();
        textwidth = rect.width();
        //速度顯示
        canvas.drawText(speed, mwidth / 2 - textwidth / 2, mheight / 2 - textheight / 2, mtxtPaint);


//        canvas.save();
        canvas.translate(mwidth / 2, mheight / 2);
        //循轉60
        canvas.rotate(60);
//        //畫速度刻度值盤
        int count = 45;
        for (int i = 0; i < count; i++) {
            if (i <= 30) {
                if (i % 5 == 0) {
                    canvas.drawLine(0, radius + 10, 0, radius + 60, tmpPaint);
//                    canvas.drawLine(0, radius, 0, radius + 30, tmpPaint);
                    //設定位元速率
                    Rect recttxt = new Rect();
                    String value = String.valueOf(i / 5 * 50);
                    tmpPaint.getTextBounds(value, 0, value.length(), recttxt);
                    valueheight = recttxt.height();
                    valuewidth = recttxt.width();
                    //文字旋轉180
                    canvas.save();
                    canvas.rotate(180, 0, radius);
//                    canvas.drawText(value, -4f, radius + 25f, tmpPaint);
                    canvas.drawText(value, -valuewidth / 2, radius + 25f - valueheight / 2, tmpPaint);
                    canvas.restore();
                } else {
                    canvas.drawLine(0, radius + 40, 0, radius + 60, tmpPaint);
//                    canvas.drawLine(0, radius + 10, 0, radius + 30, tmpPaint);
                }
                canvas.rotate(360 / count, 0f, 0f); //旋轉畫紙
            }
        }
//        canvas.restore();


        super.onDraw(canvas);
    }

    private int dipToPx(float dip) {
        float density = getContext().getResources().getDisplayMetrics().density;
        return (int) (dip * density + 0.5f * (dip >= 0 ? 1 : -1));
    }


    /**
     * 計算當前速度佔最高速度的多少
     * 240是角度
     *
     * @param delta
     */
    public void setDelta(float delta) {
        this.mspeed = delta;
        this.delta = delta * 240 / 300;
        invalidate();
    }


}複製程式碼

以上還有很多可優化及其可提取出來的屬性設定,可根據自己專案需求進行修改

SpeedTableView github地址
個人部落格

相關文章