这篇文章是基于以下两篇文章的实践:
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);
}
谢谢观赏
网友评论