上效果图:
以下标记一些绘图的核心点:
① 测量自定义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小时后回到进入时的状态(位置、角度)。
②一个循环完成后,Animation重复repeat起来,这样,一个简约而不无乐趣的时钟控件就完成啦。
所有代码在222行左右, 如果出去添加表盘背景图片, 180行足够。
以上!
附上源码地址: http://download.csdn.net/detail/donmoses/9138165点击打开链接