自定义 View 实践(二)- 简易时钟


这篇文章是基于以下两篇文章的实践:
1.自定义View - 基础
2.自定义View - Canvas - 图形绘制
3.自定义 View - Canvas - 画布操作和快照


GIF.gif

时钟的大致效果如上,用到的主要有图形的绘制,画布操作和快照。可能还是有点丑,唉,我发誓最后会做一个 B 格高的东西。

下面来说说这个自定义 View。

需要实现一个视图之前,我们首先要将它拆分成各个模块,这样能方便我们构思各个模块的实现方式。

而这个时钟我把它分为两部分:表盘和时针。其中,表盘分为三部分,圆、刻度、数字;指针有三个,时、分、秒。

1.初始化数据

绘制时钟我们需要的主要有这样一些数据:

  • 画笔
    • 时分秒
    • 刻度
    • 表盘
    • 数字
  • 数据
    • 表盘大小
    • 刻度长度
    • 指针长度
    • 指针多余长度
    • 当前时间

这里我做了简单的初始化:

    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;
        //时间
        mHour = 0;
        mMin = 0;
        mSec = 0;
    }
2.绘制表盘

1)绘制圆
我们可以看到整个时钟的中心始终在画布正中央,因此第一步,我们先将画布移到中间。为了获取移动到画布正中间需要的像素数,我们要先从 onSizeChange 方法中,获取图形大小,然后移动到画布中间绘制圆。

    @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);
        //移动到中心
        canvas.translate(mWidth / 2, mHeight / 2);
        //表盘
        canvas.drawCircle(0, 0, mRadio, mClockPaint);
    }

2)绘制刻度
显然,我们不能通过三角函数来确定每个刻度的位置,那样问题也变得困难。这里我们借助画布的旋转来实现。计算出最小格的角度为 6 度,因此,我们只要旋转 60 次。由于特殊位置需要有长度不同的线段标志,因此这里也需要加入判断。另外,刻度的绘制是旋转 360 度的,因此并不需要画布快照来保留当前状态。

//刻度
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)绘制指针
由于时分秒的“0”刻度在 12 的位置,因此,绘制指针的过程中,要以 12 到圆心的连线为准。

//指针
//时
canvas.save();
canvas.rotate(mHour % 12 / 12 * 360 + mMin % 60 / 60 * 30);// 计算需要转过的角度
canvas.drawLine(0, mHourBegin, 0, -mHourLen + mHourBegin, mHourPaint);
canvas.restore();
//分
canvas.save();
canvas.rotate(mMin % 60 / 60 * 360 + mSec % 60 / 60 * 6);// 计算需要转过的角度
canvas.drawLine(0, mMinBegin, 0, -mMinLen + mMinBegin, mMinPaint);
canvas.restore();
//秒
canvas.save();
canvas.rotate(mSec % 60 / 60 * 360);// 计算需要转过的角度
canvas.drawLine(0, mSecBegin, 0, -mSecLen + mSecBegin, mSecPaint);
canvas.restore();

4)事件
视图已经绘制好了,下面需要让这个视图动起来,这个比较简单,利用 Handler 就可以实现了。这里我们定义了四个状态,分别对应开始、停止、重置、继续。

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;
        }
    }
};

暴露出控制时间的方法:

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);
}

谢谢观赏

你可能感兴趣的:(自定义 View 实践(二)- 简易时钟)