上效果图:
以下标记一些绘图的核心点:
① 测量自定义view的大小
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getDimension(DEFAULT_WIDTH, widthMeasureSpec);
int height = getDimension(DEFAULT_HEIGHT, heightMeasureSpec);
viewW = width;
cenP.x = viewW / 2;
cenP.y = height / 2;
radius = Math.min(viewW, height) / 2;
setMeasuredDimension(width, height);
}
private int getDimension(int defaultSize, int measureSpec) {
int result;
int measureMode = MeasureSpec.getMode(measureSpec);
int measureSize = MeasureSpec.getSize(measureSpec);
if (measureMode == MeasureSpec.EXACTLY) {
result = measureSize;
} else if (measureMode == MeasureSpec.AT_MOST) {
result = Math.min(defaultSize, measureSize);
} else {
result = defaultSize;
}
return result;
}
自定义控件的一般测量测量方式, 大家温习一下
②获取系统时间,这里我使用的是Calendar,非常方便,小小的不足是返回时间是24制式
private void getTime() {
Calendar calendar = Calendar.getInstance();
int hour = calendar.get(Calendar.HOUR_OF_DAY);
int min = calendar.get(Calendar.MINUTE);
int sec = calendar.get(Calendar.SECOND);
if (hour > 12) {
hour = hour - 12;
}
angelS = 360 * sec / 60f;
angelM = 360 * min / 60f + sec / 60f;
angelH = 360 * hour / 12f + 360 * min / (60 * 12f) + 360 * sec / (60 * 60 * 12f);
angelStartS = angelS;
angelStartM = angelM;
angelStartH = angelH;
dateOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
dateOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
}
这里获取的系统时间作用:计算进入View时,时针、分针、秒针的角度——也就是一开始的初始角度。【这是最重要的一步】
之后的表针转动问题就相对简单了——获取每帧秒针角度angleS,则每帧分针角度angleM = angleS/60,每帧时针角度angleH = angle/3600。
**这里多说几句: 为了实现指针跟随系统时间,我首先想到的使用Animation,而没有使用线程去一直跑。
因为本自定义控件就是简单实现一个时钟,所有工作都可以交给主线程完成,所以使用Animation更为方便,然后通过设置周期单元duration 和 插值器Interpolator 以及循环属性repeatMode和循环次数数repeatCount,这样一个跟随系统时间,简单方便的逻辑就基本形成。
有了清晰的思路,接下来的工作就按部就班了:
首先,我们来获取绘制表盘背景所需要的Bitmap ,
然后初始化一下绘画的一些相关对象:
private void init() {
src = BitmapFactory.decodeResource(getResources(), R.drawable.iron_bg);
//初始化绘画相关
mPaint = new Paint();
mPaint.setAntiAlias(true);
cenP = new PointF();
path = new Path();
Animation animation = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
float mAngelS = 24 * 60 * 360 * interpolatedTime;
angelS = angelStartS + mAngelS;
angelM = angelStartM + mAngelS / 60f;
angelH = angelStartH + mAngelS / 3600f;
invalidate();
}
};
animation.setDuration(24 * 60 * 60 * 1000);//一天24个小时为一个周期
animation.setRepeatCount(Animation.INFINITE);
animation.setRepeatMode(Animation.REVERSE);
animation.setInterpolator(new LinearInterpolator());
startAnimation(animation);
}
有了这些工作,之后剩下的就是绘制各个图形元素了:
首先绘制背景图,这里因为表盘是圆形的,所以使用到Path,通过画布canvas.clipPath()剪裁已经添加了圆形路径的path,得到我们需要的圆形画布,然后在画布上绘制图片。
【**View 绘画的思路跟ps很相似, 当然了如果你没有使用过ps, 那么更形象的比喻就是,跟裁缝剪裁衣服一样。不同的是,裁缝更多的时候是拼接,而画布则更多的是层叠。】
mPaint.setAntiAlias(true);
path.reset();
path.addCircle(cenP.x, cenP.y, radius, Path.Direction.CW);
canvas.clipPath(path);
canvas.drawBitmap(src, 0, 0, mPaint);
path.close();
绘制好了背景,再来为表盘加一个黑色边框
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(6.18f * 2);
mPaint.setColor(Color.BLACK);
canvas.drawCircle(cenP.x, cenP.y, radius, mPaint);
接下来是什么? 。。。。。。聪明的你或许想到,接下来就是绘制表盘刻度, 然后是表针...done
但是先让我们绘制日期和星期 —— 因为前面说到了, View的绘制是层叠的,而现实中指针肯定实在日期和星期的上面, 所以必须先绘制出较底层的层次:
//日期
mPaint.setColor(Color.RED);
mPaint.setStrokeWidth(0);
mPaint.setTextSize(44);
canvas.drawText(String.valueOf(dateOfMonth), cenP.x + radius / 4, cenP.y + 22, mPaint);
//星期
mPaint.setTextSize(44);
String weekDay = null;
switch (dateOfWeek) {
case 1:
weekDay = "SUN";
break;
case 2:
weekDay = "MON";
break;
case 3:
weekDay = "TUE";
break;
case 4:
weekDay = "WED";
break;
case 5:
weekDay = "THU";
break;
case 6:
weekDay = "FRI";
break;
case 7:
weekDay = "SAT";
break;
default:
break;
}
assert weekDay != null;
canvas.drawText(weekDay, cenP.x + radius / 2, cenP.y + 22, mPaint);
那么这里,我们就来到了指针的绘制步骤啦:
//绘制时针
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setStrokeWidth(20f);
float hLen = radius / 2f;
canvas.drawLine(cenP.x, cenP.y, (float) (viewW / 2f + hLen * Math.sin(angelH * Math.PI / 180f)),
(float) (radius - hLen * Math.cos(angelH * Math.PI / 180f)), mPaint);
//绘制分针
mPaint.setColor(Color.BLUE);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setStrokeWidth(12.5f);
float mLen = 2 * radius / 3f;
canvas.drawLine(cenP.x, cenP.y, (float) (viewW / 2f + mLen * Math.sin(angelM * Math.PI / 180f)),
(float) (radius - mLen * Math.cos(angelM * Math.PI / 180f)), mPaint);
//绘制秒针
mPaint.setColor(Color.WHITE);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setStrokeWidth(5f);
float sLen = 5 * radius / 6f;
canvas.drawLine(cenP.x, cenP.y, (float) (viewW / 2f + sLen * Math.sin(angelS * Math.PI / 180f)),
(float) (radius - sLen * Math.cos(angelS * Math.PI / 180f)), mPaint);
*** 需要简单说一下,,,因为时、分、秒针长度不同, 则我们先设定各个指针的长度,然后指针长度和设定的画笔大小、颜色, 依据各自的角度绘制出一条线,即我们的指针。
***另外,时、分、秒针的绘制顺序依据 (先——时 - 分 - 秒——后),原因同上,就是个层叠问题哈
为了让三个指针看上去更加真实, 而不是简单的将三根线段堆叠在一起,这里还需要在指针轴心点绘制一个圆点,覆盖在最顶层的秒针上。
这样就像我们真实的手表一样啦——三根针一次层叠,然后一根轴心柱插在交点处,如此很好理解啦, 代码如下:
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(Color.WHITE);
canvas.drawCircle(cenP.x, cenP.y, radius / 30, mPaint);
哈哈,快到站咯...........................
忘了点什么?
对, 我们的刻度呢? ———— 为了避免遮盖,这里我们最后绘制刻度。 为了简单,我简单的绘制了1——12个文字,没有真的绘制出刻度(计算是个大而细的工作,各位可以根据自己喜好去慢慢实现, 原理都简单、相似, 但是过程复杂,呵呵( ̄▽ ̄)")
//12个点
float txtSize = 35f;
mPaint.setTextSize(txtSize);
mPaint.setStrokeWidth(3.09f);
float rR = radius - 35f;
for (int i = 0; i < 12; i++) {
String txt;
if (i == 0) {
txt = "12";
} else {
txt = String.valueOf(i);
}
canvas.drawText(
txt
, (float) (viewW / 2f - txtSize / 2f + rR * Math.sin(i * 30 * Math.PI / 180f))
, (float) (radius + txtSize / 2f - rR * Math.cos(i * 30 * Math.PI / 180f))
, mPaint);
}
-----------------------------------------------------------------------------------------------Moses 分割线---------------------------------------------------------------------------------------------------------------
最后,但绝不是最次要的来了。 ——————如何,让我们的指针跑起来——前面说了,我们使用Animation:
Animation animation = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
float mAngelS = 24 * 60 * 360 * interpolatedTime;
angelS = angelStartS + mAngelS;
angelM = angelStartM + mAngelS / 60f;
angelH = angelStartH + mAngelS / 3600f;
invalidate();
}
};
animation.setDuration(24 * 60 * 60 * 1000);//一天24个小时为一个周期
animation.setRepeatCount(Animation.INFINITE);
animation.setRepeatMode(Animation.REVERSE);
animation.setInterpolator(new LinearInterpolator());
startAnimation(animation);
注意一下几点: ①定义一个循环,单元为24小时,也就是一天。————从进入该时钟View开始,各个指针将在24小时后回到进入时的状态(位置、角度)。
所有代码在222行左右, 如果出去添加表盘背景图片, 180行足够。
以上!