自定義view之繪制模擬時鐘

原創(chuàng)發(fā)布地址

之前在自定義view之寫一個帶刪除按鈕的Edittext中簡單介紹了如何繼承Edittext實現(xiàn)點擊區(qū)域刪除全部文字盐类。

自定義view之可伸縮的圓弧與扇形中介紹了如何制作帶有動畫效果的圓弧和扇形圖。

模擬時鐘實現(xiàn)思路

前邊兩篇都是入門文章边坤,這篇算是一個基礎文章你踩,我們來制作一個模擬時鐘,與手機上的時間保持同步運轉(zhuǎn)穷遂。首先看一下我自己的做的效果圖(很low的一個界面):

可以看到在53分鐘結束到54分鐘開始的時候晾虑,時針分針秒針基本保持與時間同步(實際在繪制過程中由于三角函數(shù)的double類型轉(zhuǎn)float類型辛臊,以及π的位數(shù)仙粱,還是會有誤差)。

時鐘實現(xiàn)的難點在于如何繪制指針的重點坐標并時刻刷新保持與手機同步彻舰。此處我采用了取巧的方式伐割,后邊會詳細介紹。

初始化工作

首先同樣需要繼承view類作為父類刃唤,并實現(xiàn)幾個構造函數(shù)隔心。

private static final float threeSqure = 1.7320508075689F;
    private static final float PIE = 3.1415926535898F;

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

    public MyClock(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public MyClock(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

在init函數(shù)中定義了一系列的畫筆等工具。

private void init() {
        bgPaint = new Paint();
        bgPaint.setStyle(Paint.Style.STROKE);
        bgPaint.setColor(Color.BLACK);
        bgPaint.setStrokeWidth(10);
        bgPaint.setAntiAlias(true);
        boldNumPaint = new Paint();
        boldNumPaint.setStyle(Paint.Style.STROKE);
        boldNumPaint.setColor(Color.BLACK);
        boldNumPaint.setStrokeWidth(20);
        boldNumPaint.setAntiAlias(true);
        thinNumPaint = new Paint();
        thinNumPaint.setStyle(Paint.Style.STROKE);
        thinNumPaint.setColor(Color.BLACK);
        thinNumPaint.setStrokeWidth(10);
        thinNumPaint.setAntiAlias(true);
        secondPaint = new Paint();
        secondPaint.setStyle(Paint.Style.FILL);
        secondPaint.setColor(Color.GREEN);
        secondPaint.setAntiAlias(true);
        secondPaint.setStrokeWidth(10);
        centerPaint = new Paint();
        centerPaint.setStyle(Paint.Style.FILL);
        centerPaint.setColor(Color.BLACK);
        centerPaint.setAntiAlias(true);
        innerPaint = new Paint();
        innerPaint.setStyle(Paint.Style.FILL);
        innerPaint.setColor(Color.WHITE);
        innerPaint.setAntiAlias(true);
    }

此處指明一些需要注意的地方就是setstyle一定要設置好尚胞,F(xiàn)ILL是填充硬霍,畫出來的是實心的,STROKE是描邊笼裳,畫出來的是空心的唯卖。其實也可以用一個畫筆然后再每次繪制的時候不斷重新設置也可以粱玲。

畫筆中定義width等參數(shù)的時候一般是以px為單位,但是更多的時候我們需要以dp為單位拜轨,此處可以稍微注意一下抽减,px與dp的轉(zhuǎn)換。

我們知道橄碾,要想獲得view的實際尺寸要在onsizechange方法中卵沉。在onsizechange方法中我們獲取了一些在繪圖中會用到的尺寸,實際需要的是一個正放形法牲,所以取了區(qū)域中上邊的一個方形史汗。

protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        Log.d(TAG, "onSizeChanged");
        super.onSizeChanged(w, h, oldw, oldh);
        this.width = Math.min(w, h);
        this.height = Math.min(w, h);
        inCircle = new RectF(55, 55, width - 55, height - 55);
        outCircle = new RectF(5, 5, width - 5, height - 5);
        radius = (float) ((width - 110) / 2);
        innerCircle = new RectF(100, 100, width - 100, height - 100);

    }

暴露接口

為了讓時鐘啟動,我們需要自定一個外部可以訪問的方法來啟動時鐘:startClock()拒垃。

 public void startClock() {
        myTime = new MyTime();
        Log.d(TAG, myTime.toString());
        animatorSecond = ValueAnimator.ofFloat(setSecond(myTime), setSecond(myTime) + 2 * 60 * PIE);
        animatorMinute = ValueAnimator.ofFloat(setMinute(myTime), setMinute(myTime) + 2 * PIE);
        animatorHour = ValueAnimator.ofFloat(setHour(myTime), setHour(myTime) + 6 * PIE / 180);

        animatorSecond.removeAllUpdateListeners();
        animatorMinute.removeAllUpdateListeners();
        animatorHour.removeAllUpdateListeners();

        animatorSecond.setDuration(60 * 1000 * 60);
        animatorMinute.setDuration(60 * 1000 * 60);
        animatorHour.setDuration(60 * 1000 * 60);

        animatorSecond.setInterpolator(new LinearInterpolator());
        animatorMinute.setInterpolator(new LinearInterpolator());
        animatorHour.setInterpolator(new LinearInterpolator());

        animatorSecond.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                passSecondArc = (float) animation.getAnimatedValue();
                postInvalidate();
            }
        });

        animatorMinute.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                passMinuteArc = (float) animation.getAnimatedValue();
            }
        });

        animatorHour.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                passHourArc = (float) animation.getAnimatedValue();
            }
        });
        AnimatorSet set = new AnimatorSet();
        set.removeAllListeners();
        set.playTogether(animatorSecond, animatorMinute, animatorHour);
        set.start();

    }

這個方法中首先定義了一個內(nèi)部類MyTime停撞,用來獲取當前時間的時分秒。內(nèi)部類的核心方法:

public MyTime() {
            Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT+8"));
            year = calendar.get(Calendar.YEAR);
            month = calendar.get(Calendar.MONTH);
            day = calendar.get(Calendar.DAY_OF_MONTH);
            hour = calendar.get(Calendar.HOUR_OF_DAY);
            min = calendar.get(Calendar.MINUTE);
            sec = calendar.get(Calendar.SECOND);
        }

計算時間的起始位置

我們定義了三個動畫時間引擎恶复,這三個引擎分別負責時針怜森、分針速挑、秒針的運動谤牡。設置三個指針的起始值要根據(jù)我們獲取的當前時間來定義:

private float setSecond(MyTime myTime) {
        float passSecond = myTime.getSec();
        return 6 * passSecond / 180 * PIE + PIE / 2;
    }

此處要復習一下三角函數(shù)的相關知識。

我們的起始位置是在屏幕的最左邊高度的中點姥宝,但是這個位置并不是我們需要的12點起始位置翅萤,為了公式計算方便,我們需要的是將他順時針旋轉(zhuǎn)90度以后的位置腊满,也就是屏幕寬度的中點高度的起點位置套么。

  • 秒針的計算:
    一周是360度,也就是2π碳蛋,1分鐘60s胚泌,每秒經(jīng)過的角度就是6度。

首先獲取當前的秒的時間肃弟,計算經(jīng)過的秒數(shù)玷室,然后換算成弧度,最后加上π的一半笤受,就是我們要展現(xiàn)出來的弧度穷缤。此處使用的單位是float單精度浮點型。這就是我們設置的時間引擎的起始值箩兽。

這個demo中我設定的時間是1個小時的動畫津肛,所以一個小時秒針會經(jīng)過60圈,最后的中點值就設為了起始值+60*2π汗贫。

  • 分針的計算
private float setMinute(MyTime myTime) {
        float passMinute = myTime.getMin() * 6 + myTime.getSec() / 10;
        return passMinute / 180 * PIE + PIE / 2;
    }

一小時是60分鐘身坐,所以每經(jīng)過1分鐘要經(jīng)過6度秸脱。為了使程序看起來更準確,我們還要計算經(jīng)過的秒數(shù)掀亥,而不至于在一開始就在一個不準確的位置撞反。60秒鐘經(jīng)過6度,則每秒鐘經(jīng)過0.1度搪花,粗略計算出經(jīng)過的分鐘角度是myTime.getMin() * 6 + myTime.getSec() / 10遏片,然后換算成弧度并加上π/2。

  • 時針的計算
    時針計算與分針計算相似撮竿,只是注意一小時走過的角度是30度吮便,所以在換算的時候要注意經(jīng)過的小時和經(jīng)過的分鐘的角度關系。

然后我們?yōu)槊總€引擎加上了監(jiān)聽方法幢踏,這個方法會將在每一個時刻的具體位置返回給我們髓需。注意默認的插值器是低速-高度-低速這樣的速度數(shù)值變化,明顯不是我們要的結果房蝉,我們要用線性插值器來獲得一個勻速的變化僚匆。然后啟動動畫引擎集合。

繪制

在onDraw方法中我們要繪制所有的一切圖形搭幻。

        drawBackGround(canvas);
        draw0369(canvas);
        drawHourGap(canvas);
        drawInnerCircle(canvas);
        drawM(canvas);
        drawS(canvas);
        drawH(canvas);
        drawCenter(canvas);
  1. drawBackGround(canvas)
private void drawBackGround(Canvas canvas) {
        bgPaint.setColor(Color.WHITE);
        bgPaint.setStyle(Paint.Style.FILL);
        canvas.drawRect(0, 0, width, height, bgPaint);
        bgPaint.setColor(Color.BLACK);
        bgPaint.setStyle(Paint.Style.STROKE);
        canvas.drawArc(outCircle, 0, 360, false, bgPaint);
        canvas.drawArc(inCircle, 0, 360, false, bgPaint);
    }

這個是繪制背景圓咧擂,效果是這樣的

在上一篇文章中已經(jīng)介紹了如何使用paint來畫扇形和弧線,這里就不介紹了檀蹋,只要設置起點和重點為0-360即可松申。

  1. draw0369(canvas)
private void draw0369(Canvas canvas) {
        canvas.drawLine(width / 2, 55, width / 2, height - 55, boldNumPaint);
        canvas.drawLine(55, height / 2, width - 55, height / 2, boldNumPaint);
    }

這個是繪制3點6點9點12點的位置。我們使用line加粗實現(xiàn)的俯逾。
完成后效果如下


  1. drawHourGap(canvas);
private void drawHourGap(Canvas canvas) {
        canvas.drawLine(radius * (1 - threeSqure / 2) + 55,
                (height - radius) / 2,
                width - 55 - radius * (1 - threeSqure / 2),
                (height + radius) / 2, thinNumPaint);
        canvas.drawLine(radius * (1 - threeSqure / 2) + 55,
                (height + radius) / 2,
                width - 55 - radius * (1 - threeSqure / 2),
                (height - radius) / 2, thinNumPaint);
        canvas.drawLine(radius / 2 + 55,
                height / 2 - radius * threeSqure / 2,
                width - 55 - radius / 2,
                height / 2 + radius * threeSqure / 2, thinNumPaint);
        canvas.drawLine(radius / 2 + 55,
                height / 2 + radius * threeSqure / 2,
                width - 55 - radius / 2,
                height / 2 - radius * threeSqure / 2, thinNumPaint);
    }

這個是繪制其他小時的贸桶,用的是細的line實現(xiàn)。注意角度換算關系桌肴,因為要計算時間的角度皇筛,所以三角函數(shù)關系還是要把這些基本的計算掌握。效果如下:


4.drawInnerCircle(canvas)

這個和1是一樣的坠七,只是要繪制實心將中間的線擋住水醋,所以paint要設置為FILL。
效果如下:

5.drawM(canvas) drawS(canvas) drawH(canvas);

private void drawM(Canvas canvas) {
        secondPaint.setColor(Color.BLUE);
        secondPaint.setStrokeWidth(20);
        canvas.drawLine(width / 2, height / 2,
                height / 2 - (radius - 80) * (float) Math.cos(passMinuteArc),
                width / 2 - (radius - 80) * (float) Math.sin(passMinuteArc),
                secondPaint);
    }

主要看一下這個計算過程灼捂,起始坐標是我們的中心點位置离例,而終點的x軸是中心點減去經(jīng)過角度的余弦值,同樣可計算得到y(tǒng)悉稠。

  1. drawCenter(canvas);
    最后我們做一個改在所有指針中心上的蓋子宫蛆。
    最終效果:

下一節(jié)我們將介紹如何繪制一個日歷,并介紹為何暴露出來的方法startTime會在所有的重寫方法之前執(zhí)行。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末耀盗,一起剝皮案震驚了整個濱河市想虎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌叛拷,老刑警劉巖舌厨,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異忿薇,居然都是意外死亡裙椭,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門署浩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來揉燃,“玉大人,你說我怎么就攤上這事筋栋〈短溃” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵弊攘,是天一觀的道長抢腐。 經(jīng)常有香客問我,道長襟交,這世上最難降的妖魔是什么迈倍? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮婿着,結果婚禮上授瘦,老公的妹妹穿的比我還像新娘醋界。我一直安慰自己竟宋,他們只是感情好,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布形纺。 她就那樣靜靜地躺著丘侠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪逐样。 梳的紋絲不亂的頭發(fā)上蜗字,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機與錄音脂新,去河邊找鬼挪捕。 笑死,一個胖子當著我的面吹牛争便,可吹牛的內(nèi)容都是我干的级零。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼躲叼,長吁一口氣:“原來是場噩夢啊……” “哼届谈!你這毒婦竟也來了?” 一聲冷哼從身側響起饰迹,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤序调,失蹤者是張志新(化名)和其女友劉穎醉锅,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體发绢,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡硬耍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了边酒。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片默垄。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖甚纲,靈堂內(nèi)的尸體忽然破棺而出口锭,到底是詐尸還是另有隱情,我是刑警寧澤介杆,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布鹃操,位于F島的核電站,受9級特大地震影響春哨,放射性物質(zhì)發(fā)生泄漏荆隘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一赴背、第九天 我趴在偏房一處隱蔽的房頂上張望椰拒。 院中可真熱鬧,春花似錦凰荚、人聲如沸燃观。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽缆毁。三九已至,卻和暖如春到涂,著一層夾襖步出監(jiān)牢的瞬間脊框,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工践啄, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留浇雹,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓屿讽,卻偏偏與公主長得像昭灵,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

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