自定義 View 實(shí)踐(二)- 簡易時(shí)鐘


這篇文章是基于以下兩篇文章的實(shí)踐:
1.自定義View - 基礎(chǔ)
2.自定義View - Canvas - 圖形繪制
3.自定義 View - Canvas - 畫布操作和快照


GIF.gif

時(shí)鐘的大致效果如上,用到的主要有圖形的繪制,畫布操作和快照。可能還是有點(diǎn)丑,唉,我發(fā)誓最后會(huì)做一個(gè) B 格高的東西。

下面來說說這個(gè)自定義 View。

需要實(shí)現(xiàn)一個(gè)視圖之前,我們首先要將它拆分成各個(gè)模塊,這樣能方便我們構(gòu)思各個(gè)模塊的實(shí)現(xiàn)方式。

而這個(gè)時(shí)鐘我把它分為兩部分:表盤和時(shí)針。其中,表盤分為三部分,圓、刻度、數(shù)字;指針有三個(gè),時(shí)、分、秒。

1.初始化數(shù)據(jù)

繪制時(shí)鐘我們需要的主要有這樣一些數(shù)據(jù):

  • 畫筆
    • 時(shí)分秒
    • 刻度
    • 表盤
    • 數(shù)字
  • 數(shù)據(jù)
    • 表盤大小
    • 刻度長度
    • 指針長度
    • 指針多余長度
    • 當(dāng)前時(shí)間

這里我做了簡單的初始化:

    private void init() {
        initBasePaint();
        initClockPaint();
        initHourPaint();
        initMinPaint();
        initSecPaint();
        initTextPaint();

        //表盤半徑
        mRadio = 400;
        //刻度長度
        mSmallTick = 20;
        mMidTick = 40;
        mBigTick = 60;
        //指針長度
        mHourLen = 210;
        mMinLen = 280;
        mSecLen = 350;
        //指針多余長度
        mHourBegin = 70;
        mMinBegin = 70;
        mSecBegin = 70;
        //時(shí)間
        mHour = 0;
        mMin = 0;
        mSec = 0;
    }
2.繪制表盤

1)繪制圓
我們可以看到整個(gè)時(shí)鐘的中心始終在畫布正中央,因此第一步,我們先將畫布移到中間。為了獲取移動(dòng)到畫布正中間需要的像素?cái)?shù),我們要先從 onSizeChange 方法中,獲取圖形大小,然后移動(dòng)到畫布中間繪制圓。

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //移動(dòng)到中心
        canvas.translate(mWidth / 2, mHeight / 2);
        //表盤
        canvas.drawCircle(0, 0, mRadio, mClockPaint);
    }

2)繪制刻度
顯然,我們不能通過三角函數(shù)來確定每個(gè)刻度的位置,那樣問題也變得困難。這里我們借助畫布的旋轉(zhuǎn)來實(shí)現(xiàn)。計(jì)算出最小格的角度為 6 度,因此,我們只要旋轉(zhuǎn) 60 次。由于特殊位置需要有長度不同的線段標(biāo)志,因此這里也需要加入判斷。另外,刻度的繪制是旋轉(zhuǎn) 360 度的,因此并不需要畫布快照來保留當(dāng)前狀態(tài)。

//刻度
for (int i = 0; i < 60; i++) {
    int tick = i % 15 == 0 ? // 3 6 9 12 
            mBigTick : 
            (i % 5 == 0 ?  // 1 2 4 5 7 8 10 11
                    mMidTick : mSmallTick); // 分
    canvas.drawLine(0, mRadio, 0, mRadio - tick, mClockPaint);
    canvas.drawLine(0, -mRadio, 0, -mRadio + tick, mClockPaint);
    canvas.rotate(6);
}

3)繪制指針
由于時(shí)分秒的“0”刻度在 12 的位置,因此,繪制指針的過程中,要以 12 到圓心的連線為準(zhǔn)。

//指針
//時(shí)
canvas.save();
canvas.rotate(mHour % 12 / 12 * 360 + mMin % 60 / 60 * 30);// 計(jì)算需要轉(zhuǎn)過的角度
canvas.drawLine(0, mHourBegin, 0, -mHourLen + mHourBegin, mHourPaint);
canvas.restore();
//分
canvas.save();
canvas.rotate(mMin % 60 / 60 * 360 + mSec % 60 / 60 * 6);// 計(jì)算需要轉(zhuǎn)過的角度
canvas.drawLine(0, mMinBegin, 0, -mMinLen + mMinBegin, mMinPaint);
canvas.restore();
//秒
canvas.save();
canvas.rotate(mSec % 60 / 60 * 360);// 計(jì)算需要轉(zhuǎn)過的角度
canvas.drawLine(0, mSecBegin, 0, -mSecLen + mSecBegin, mSecPaint);
canvas.restore();

4)事件
視圖已經(jīng)繪制好了,下面需要讓這個(gè)視圖動(dòng)起來,這個(gè)比較簡單,利用 Handler 就可以實(shí)現(xiàn)了。這里我們定義了四個(gè)狀態(tài),分別對(duì)應(yīng)開始、停止、重置、繼續(xù)。

public static final int STEP_START = 0;
public static final int STEP_STOP = 1;
public static final int STEP_RESET = 2;
public static final int STEP_NEXT = 3;

在 handler 中處理事件:

private boolean bContinue;

private Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        switch (msg.what) {
            case STEP_START:
                bContinue = true;
                sendEmptyMessage(STEP_NEXT);
                break;
            case STEP_NEXT:
                if (bContinue) {
                    timeUp();
                    invalidate();
                    sendEmptyMessageDelayed(STEP_NEXT, 1 * 1000);
                }
                break;
            case STEP_STOP:
                bContinue = false;
                handler.removeCallbacksAndMessages(null);
                break;
            case STEP_RESET:
                bContinue = true;
                mHour = 0;
                mMin = 0;
                mSec = 0;
                handler.removeCallbacksAndMessages(null);
                sendEmptyMessage(STEP_NEXT);
                break;
        }
    }
};

暴露出控制時(shí)間的方法:

public void setTime(int h, int m, int s) {
    mHour = h;
    mMin = m;
    mSec = s;
    invalidate();
}

public void start() {
    handler.sendEmptyMessage(STEP_START);
}

public void stop(){
    handler.sendEmptyMessage(STEP_STOP);
}

public void reset(){
    handler.sendEmptyMessage(STEP_RESET);
}

謝謝觀賞
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容