自定義View繪制時(shí)鐘表盤

重要:原創(chuàng),轉(zhuǎn)載注明出處trueMi-簡書

首先看下效果圖:

表盤時(shí)鐘演示.gif

實(shí)現(xiàn)步驟:

  • 繪制表盤[刻度,數(shù)字]
  • 繪制指針
  • 讓指針走起來~

具體如下:

繪制表盤:

首先需要計(jì)算出刻度的起點(diǎn)和終點(diǎn)坐標(biāo)值,這里我們通過構(gòu)建兩個(gè)半徑不同的同心圓,大圓半徑減小圓半徑,就可以得到一條刻度,只用改變角度,就可以獲取所有刻度:
刻度.png
    /**
     * 通過改變角度值,獲取不同角度方向的外圓一點(diǎn)到圓心連線過內(nèi)圓一點(diǎn)的路徑坐標(biāo)集合
     * @param x0 圓心x
     * @param y0 圓心y
     * @param outRadius 外圓半徑
     * @param innerRadius 內(nèi)圓半徑
     * @param angle 角度
     * @return 返回
     */
    private float[] getDialPaths(int x0,int y0,int outRadius,int innerRadius,int angle){
        float[] paths = new float[4];
        paths[0]  = (float) (x0 + outRadius * Math.cos(angle * Math.PI / 180));
        paths[1]  = (float) (y0 + outRadius * Math.sin(angle * Math.PI / 180));
        paths[2]  = (float) (x0 + innerRadius * Math.cos(angle * Math.PI / 180));
        paths[3]  = (float) (y0 + innerRadius * Math.sin(angle * Math.PI / 180));
        return paths;
    }

秒針刻度間隔360/60 = 6 度,循環(huán)繪制60次,每一次角度加6,就可以了;繪制代碼如下:

for (int i = 0; i < 60 ; i++) {
            if (i % 5 == 0){
                //獲取刻度路徑
                float[] dialKdPaths = getDialPaths(halfMinLength, halfMinLength, halfMinLength - 8, halfMinLength * 5 / 6, -i * 6);
                canvas.drawLines(dialKdPaths,paintKd30);
                float[] dialPathsStr = getDialPaths(halfMinLength, halfMinLength, halfMinLength - 8, halfMinLength * 3 / 4, -i * 6);
                canvas.drawText(strKedu[i/5],dialPathsStr[2] - 16,dialPathsStr[3] + 14,paintKd30Text);
                continue;
            }
            float[] dialKdPaths = getDialPaths(halfMinLength, halfMinLength, halfMinLength - 8, halfMinLength * 7 / 8, -i * 6);
            canvas.drawLines(dialKdPaths,paintKdSecond);
        }
繪制指針和旋轉(zhuǎn)指針

這里的重點(diǎn)在于對指針旋轉(zhuǎn)的理解:
表盤旋轉(zhuǎn).png

通過上圖可以看到,我們通過旋轉(zhuǎn)畫布,然后繪制指針,最后恢復(fù)畫布,從而改變了指針的指向.
具體操作過程是:

  1. 保存已經(jīng)繪制畫面
  2. 以一定角度旋轉(zhuǎn)畫布
  3. 繪制指針
  4. 恢復(fù)畫布角度
    代碼如下:以時(shí)針繪制為例
        //時(shí)針繪制
        canvas.save(); //保存之前內(nèi)容
        canvas.rotate(angleHour,halfMinLength,halfMinLength); //旋轉(zhuǎn)的是畫布,從而得到指針旋轉(zhuǎn)的效果
        canvas.drawLine(halfMinLength,halfMinLength,halfMinLength,halfMinLength*3/4,paintHour);
        canvas.restore(); //恢復(fù)
讓時(shí)間走起來

通過實(shí)時(shí)的計(jì)算時(shí)針,分針,秒針的角度,然后通知重新繪制畫面,我們就看到時(shí)間在走動(dòng).

/**
     * 更新時(shí)分秒針的角度,開始繪制
     */
    public void startRun(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (drawable){
                    try {
                        Thread.sleep(1000); // 睡1s
                        updataAngleSecond(); //更新秒針角度
                        updataAngleMinute(); //更新分針角度
                        updataAngleHour(); //更新時(shí)針角度
                        postInvalidate(); //重新繪制
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }

完整代碼如下:

package com.truemi.dialapplication;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.view.View;
import java.util.Calendar;


public class DialView extends View {

    private boolean drawable = true; //是否可以繪制
    private int halfMinLength; //最小寬/高的一半長度
    private Paint paintKd30; //時(shí)針刻度線畫筆
    private Paint paintKd30Text; // 時(shí)針數(shù)字畫筆
    private Paint paintKdSecond; //秒針刻度線畫筆
    private Paint paintHour;  //時(shí)針畫筆
    private Paint paintCircleBar;//指針圓心畫筆
    private Paint paintMinute; //分針畫筆
    private Paint paintSecond; //秒針畫筆
    private float angleHour; //時(shí)針旋轉(zhuǎn)角度
    private float angleMinute; //分針旋轉(zhuǎn)角度
    private float angleSecond; //秒針旋轉(zhuǎn)角度
    private int cuurSecond; //當(dāng)前秒
    private int cuurMinute; //當(dāng)前分
    private int cuurHour; //當(dāng)前時(shí)
    private Calendar mCalendar;
    private boolean isMorning = true; //上午/下午
    private String[] strKedu = {"3","2","1","12","11","10","9","8","7","6","5","4"};


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

    public DialView(Context context, AttributeSet attrs) {
        this(context, attrs,-1);
    }

    public DialView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        initPaint(); //初始化畫筆
        initTime(); //初始化時(shí)間

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        halfMinLength = Math.min(width,height) / 2;
        System.out.println(halfMinLength);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //表盤刻度繪制
        for (int i = 0; i < 60 ; i++) {
            if (i % 5 == 0){
                //獲取刻度路徑
                float[] dialKdPaths = getDialPaths(halfMinLength, halfMinLength, halfMinLength - 8, halfMinLength * 5 / 6, -i * 6);
                canvas.drawLines(dialKdPaths,paintKd30);
                float[] dialPathsStr = getDialPaths(halfMinLength, halfMinLength, halfMinLength - 8, halfMinLength * 3 / 4, -i * 6);
                canvas.drawText(strKedu[i/5],dialPathsStr[2] - 16,dialPathsStr[3] + 14,paintKd30Text);
                continue;
            }
            float[] dialKdPaths = getDialPaths(halfMinLength, halfMinLength, halfMinLength - 8, halfMinLength * 7 / 8, -i * 6);
            canvas.drawLines(dialKdPaths,paintKdSecond);
        }
        //指針繪制
        //時(shí)針繪制
        canvas.save(); //保存之前內(nèi)容
        canvas.rotate(angleHour,halfMinLength,halfMinLength); //旋轉(zhuǎn)的是畫布,從而得到指針旋轉(zhuǎn)的效果
        canvas.drawLine(halfMinLength,halfMinLength,halfMinLength,halfMinLength*3/4,paintHour);
        canvas.restore(); //恢復(fù)
        //繪制分針
        canvas.save();
        canvas.rotate(angleMinute,halfMinLength,halfMinLength); //旋轉(zhuǎn)的是畫布,從而得到指針旋轉(zhuǎn)的效果
        canvas.drawLine(halfMinLength,halfMinLength,halfMinLength,halfMinLength/2,paintMinute);
        paintCircleBar.setColor(Color.rgb(75,75,75));
        paintCircleBar.setShadowLayer(4,4,8,Color.argb(70,40,40,40));
        canvas.drawCircle(halfMinLength,halfMinLength,24,paintCircleBar);
        canvas.restore();
        //繪制秒針
        canvas.save();
        canvas.rotate(angleSecond,halfMinLength,halfMinLength); //旋轉(zhuǎn)的是畫布,從而得到指針旋轉(zhuǎn)的效果
        canvas.drawLine(halfMinLength,halfMinLength + 40,halfMinLength,halfMinLength / 4 - 20,paintSecond);
        paintCircleBar.setColor(Color.rgb(178,34,34));
        paintCircleBar.setShadowLayer(4,4,8,Color.argb(50,80,0,0));
        canvas.drawCircle(halfMinLength,halfMinLength,12,paintCircleBar);
        canvas.restore();
    }

    /**
     * 初始化時(shí),分,秒
     */
    private void initTime() {
        mCalendar = Calendar.getInstance();
        cuurHour = mCalendar.get(Calendar.HOUR_OF_DAY);
        cuurMinute = mCalendar.get(Calendar.MINUTE);
        cuurSecond = mCalendar.get(Calendar.SECOND);
        if (cuurHour >= 12){
            cuurHour = cuurHour - 12;
            isMorning = false;
        }else{
            isMorning = true;
        }
        angleSecond = cuurSecond * 6f;
        angleMinute = cuurMinute * 6f;
        angleHour = cuurHour * 6f * 5f;
    }

    /**
     * 更新時(shí)分秒針的角度,開始繪制
     */
    public void startRun(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (drawable){
                    try {
                        Thread.sleep(1000); // 睡1s
                        updataAngleSecond(); //更新秒針角度
                        updataAngleMinute(); //更新分針角度
                        updataAngleHour(); //更新時(shí)針角度
                        postInvalidate(); //重新繪制
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }

    private void updataAngleHour() {
        //更新時(shí)針角度
        angleHour = angleHour + (30f/3600);
        if (angleHour >= 360){
            angleHour = 0;
            cuurHour = 0;
        }
    }

    private void updataAngleMinute() {
        //更新分針角度
        angleMinute = angleMinute + 0.1f;
        if (angleMinute >= 360){
            angleMinute = 0;
            cuurMinute = 0;
            cuurHour += 1;
        }
    }

    private void updataAngleSecond() {
        //更新秒針角度
        angleSecond = angleSecond + 6;
        cuurSecond += 1;
        if (angleSecond >= 360){
            angleSecond = 0;
            cuurSecond = 0;
            cuurMinute += 1;
            //一分鐘同步一次本地時(shí)間
            mCalendar = Calendar.getInstance();
            cuurHour = mCalendar.get(Calendar.HOUR_OF_DAY);
            cuurMinute = mCalendar.get(Calendar.MINUTE);
            cuurSecond = mCalendar.get(Calendar.SECOND);
            if (cuurHour >= 12){
                cuurHour = cuurHour - 12;
                isMorning = false;
            }else{
                isMorning = true;
            }
            angleSecond = cuurSecond * 6f;
            angleMinute = cuurMinute * 6f;
            angleHour = cuurHour * 6f * 5f;
        }
    }
    /**
     * 停止繪制
     */
    public void stopDrawing(){
        drawable = false;
    }

    /**
     * 通過改變角度值,獲取不同角度方向的外圓一點(diǎn)到圓心連線過內(nèi)圓一點(diǎn)的路徑坐標(biāo)集合
     * @param x0 圓心x
     * @param y0 圓心y
     * @param outRadius 外圓半徑
     * @param innerRadius 內(nèi)圓半徑
     * @param angle 角度
     * @return 返回
     */
    private float[] getDialPaths(int x0,int y0,int outRadius,int innerRadius,int angle){
        float[] paths = new float[4];
        paths[0]  = (float) (x0 + outRadius * Math.cos(angle * Math.PI / 180));
        paths[1]  = (float) (y0 + outRadius * Math.sin(angle * Math.PI / 180));
        paths[2]  = (float) (x0 + innerRadius * Math.cos(angle * Math.PI / 180));
        paths[3]  = (float) (y0 + innerRadius * Math.sin(angle * Math.PI / 180));
        return paths;
    }

    /**
     * 初始化畫筆參數(shù)
     */
    private void initPaint() {
        paintKd30 = new Paint();
        paintKd30.setStrokeWidth(8);
        paintKd30.setColor(Color.rgb(75,75,75));
        paintKd30.setAntiAlias(true);
        paintKd30.setDither(true);
        paintKd30.setStrokeCap(Paint.Cap.ROUND);

        paintKd30Text = new Paint();
        paintKd30Text.setTextAlign(Paint.Align.LEFT); //左對齊
        paintKd30Text.setStrokeWidth(6); //設(shè)置寬度
        paintKd30Text.setTextSize(40); //文字大小
        paintKd30Text.setTypeface(Typeface.DEFAULT_BOLD); //加粗
        paintKd30Text.setColor(Color.rgb(75,75,75)); //畫筆顏色
        paintKd30Text.setAntiAlias(true); //抗鋸齒
        paintKd30Text.setDither(true); //抖動(dòng)
        paintKd30Text.setStrokeCap(Paint.Cap.ROUND); //筆尖圓角
        paintKd30Text.setShadowLayer(4,2,4,Color.argb(60,90,90,90)); //陰影

        paintKdSecond = new Paint();
        paintKdSecond.setStrokeWidth(6);
        paintKdSecond.setColor(Color.rgb(75,75,75));
        paintKdSecond.setAntiAlias(true);
        paintKdSecond.setDither(true);
        paintKdSecond.setStrokeCap(Paint.Cap.ROUND);
        paintKdSecond.setShadowLayer(4,5,10,Color.argb(50,80,80,80));

        paintHour = new Paint();
        paintHour.setStrokeWidth(30);
        paintHour.setColor(Color.rgb(75,75,75));
        paintHour.setAntiAlias(true);
        paintHour.setDither(true);
        paintHour.setStrokeCap(Paint.Cap.ROUND);
        paintHour.setShadowLayer(4,5,10,Color.argb(50,80,80,80));

        paintCircleBar = new Paint();
        paintCircleBar.setStrokeWidth(6);
//        paintCircleBar.setColor(Color.rgb(178,34,34));
        paintCircleBar.setAntiAlias(true);
        paintCircleBar.setDither(true);
        paintCircleBar.setStrokeCap(Paint.Cap.ROUND);
//        paintCircleBar.setShadowLayer(4,5,10,Color.argb(100,80,80,80));

        paintMinute = new Paint();
        paintMinute.setStrokeWidth(30);
        paintMinute.setColor(Color.rgb(75,75,75));
        paintMinute.setAntiAlias(true);
        paintMinute.setDither(true);
        paintMinute.setStrokeCap(Paint.Cap.ROUND);
        paintMinute.setShadowLayer(4,5,10,Color.rgb(80,80,80));

        paintSecond = new Paint();
        paintSecond.setStrokeWidth(6);
        paintSecond.setColor(Color.rgb(180,30,30));
        paintSecond.setAntiAlias(true);
        paintSecond.setDither(true);
        paintSecond.setStrokeCap(Paint.Cap.ROUND);
        paintSecond.setShadowLayer(4,2,10,Color.argb(100,90,90,90));

    }
}

代碼中有比較詳細(xì)的注釋,有問題可以留言討論哦~~~

重要:轉(zhuǎn)載注明出處

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末笼呆,一起剝皮案震驚了整個(gè)濱河市坤检,隨后出現(xiàn)的幾起案子恶守,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡柠辞,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進(jìn)店門醉箕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钾腺,“玉大人,你說我怎么就攤上這事讥裤》虐簦” “怎么了?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵己英,是天一觀的道長间螟。 經(jīng)常有香客問我,道長损肛,這世上最難降的妖魔是什么厢破? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮治拿,結(jié)果婚禮上摩泪,老公的妹妹穿的比我還像新娘。我一直安慰自己劫谅,他們只是感情好见坑,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布嚷掠。 她就那樣靜靜地躺著,像睡著了一般荞驴。 火紅的嫁衣襯著肌膚如雪不皆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天熊楼,我揣著相機(jī)與錄音霹娄,去河邊找鬼。 笑死鲫骗,一個(gè)胖子當(dāng)著我的面吹牛犬耻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播执泰,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼香追,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了坦胶?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤晴楔,失蹤者是張志新(化名)和其女友劉穎顿苇,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體税弃,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡纪岁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了则果。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片幔翰。...
    茶點(diǎn)故事閱讀 39,773評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖西壮,靈堂內(nèi)的尸體忽然破棺而出遗增,到底是詐尸還是另有隱情,我是刑警寧澤款青,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布做修,位于F島的核電站,受9級特大地震影響抡草,放射性物質(zhì)發(fā)生泄漏饰及。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一康震、第九天 我趴在偏房一處隱蔽的房頂上張望燎含。 院中可真熱鬧,春花似錦腿短、人聲如沸屏箍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽铣除。三九已至谚咬,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間尚粘,已是汗流浹背择卦。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留郎嫁,地道東北人秉继。 一個(gè)月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像泽铛,于是被迫代替她去往敵國和親尚辑。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評論 2 354

推薦閱讀更多精彩內(nèi)容