从事Android开发也一段时间了,一直有作云笔记的习惯,可是博客不怎么写。最近给本身定了个计划,坚持每周至少写三个自定义控件,所谓熟能生巧呀。做为第一篇写的博客,给你们带来用SurfaceView打造的自定义时钟。PS:最近看见洋神的一篇推送,有人写了这个,本身看着效果图就写了哈哈,至少效果感受还阔以O(∩_∩)Ojava
先看下这炫酷的效果哈哈(时钟上的横线是录制的缘由,效果是没有那根线的,不影响哈):git
先简单介绍一下surfaceView吧,surfaceView区别于普通的View,其拥有如下特色:github
①拥有独立的绘图表面。canvas
②surfaceView须要在宿主上挖一个洞来显示本身。框架
③其的UI绘制能够在独立的进程中进行,使用双缓冲机制,不会阻塞主线程的UI操做。ide
那么,喜欢刨根问底的朋友们又会有疑问了,到底什么是双缓冲机制?
函数
在surfaceView里面,所谓的双缓冲机制其实就是说SurfaceView分front和back两块画布,这两块画布会相互交替来显示,每post一次交替一次,交替后显示在surfaceView上面。(这只是查阅资料以后的粗略理解),有兴趣的朋友能够本身深刻研究哈O(∩_∩)Opost
那么为何要使用双缓冲机制?学习
那是为了解决某种状况下(例如游戏场景)UI反复的局部刷新带来的闪烁问题。this
好了,基本知识点已经介绍完毕,我们开始撸码咯\(^o^)/~
首先,要使用surfaceView,一些基本的代码使有套路的。一般一个surfaceView的基本框架以下:
public class ClockSurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable { private Canvas mCanvas; private Paint mPaint; private boolean isDrawing; private SurfaceHolder mHolder; public ClockSurfaceView(Context context) { super(context); init(); } public ClockSurfaceView(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { mHolder = getHolder(); mHolder.addCallback(this); setFocusable(true); setFocusableInTouchMode(true); setKeepScreenOn(true); mPaint = new Paint(); mPaint.setColor(Color.BLACK); mPaint.setAntiAlias(true); mPaint.setStyle(Paint.Style.STROKE); } @Override public void surfaceCreated(SurfaceHolder holder) { new Thread(this).start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { } private void draw() { try { mCanvas = mHolder.lockCanvas(); //draw sth } catch (Exception e) { } finally { if (mCanvas != null) mHolder.unlockCanvasAndPost(mCanvas);//这里确保每次都能提交 } } @Override public void run() { if (isDrawing) { draw(); } } }说时迟,那时快,基本框架的代码已经撸完。毕竟做为LOL青铜段的大神,这手速,不谈了
那么如今就能够开始画啦:
首先,咱们先把时钟的圆盘画出来,代码很简单:
mCanvas.drawColor(Color.WHITE);//注意必定要刷屏 mPaint.setColor(Color.BLACK); mCanvas.drawCircle(getWidth() / 2, getHeight() / 2, mRadius, mPaint);
而后是画刻度和数字:
注意:这里画刻度和数字就须要捡回来高中的三角函数的数学了,做为曾经的数学课表明,so easy啦
弧度的图形表示:
求坐标点并修正坐标系的三角函数公式:
private float calculateX(int radius, double radian) { return (float) (radius * Math.cos(radian)) + mCenterX; } private float calculateY(int radius, double radian) { return -(float) (radius * Math.sin(radian)) + mCenterY; }
public static final double= 2 * Math.PI / 60;
须要注意的是,咱们的整点的格子会有数字而且刻度线会加粗处理,所以,咱们用currentPosition来记录当前所画的是第几个刻度,每5的整倍数刻度线就会加粗加长处理(好羞涩)
/** * 画刻度和数字 */ private void drawScaleAndNum() { for (double i = HALF_PI; i > -1.5 * Math.PI; i -= intervalRadian) { currentPosition += 1; float outCircleX = calculateX(mRadius, i); float outCircleY = calculateY(mRadius, i);//弧度的Y轴和Android的Y轴方向相反,所以取反 float inCircleX;//从圆盘上的点画起,肯定终点的位置圆,从而画刻度线 float intCircleY; if ((currentPosition - 1) / 5 < 12 && (currentPosition - 1) % 5 == 0) {//整点 inCircleX = calculateX(mRadius - DensityUtil.dip2px(mContext, 12.5f), i); intCircleY = calculateY(mRadius - DensityUtil.dip2px(mContext, 12.5f), i); int hourNum = (currentPosition - 1) / 5 == 0 ? 12 : (currentPosition - 1) / 5; drawNum(mCanvas, hourNum + "", calculateX(mRadius - DensityUtil.dip2px(mContext, 30), i), calculateY(mRadius - DensityUtil.dip2px(mContext, 30), i)); mPaint.setStrokeWidth(4);//整点的刻度线加粗 } else { inCircleX = calculateX(mRadius - DensityUtil.dip2px(mContext, 5), i); intCircleY = calculateY(mRadius - DensityUtil.dip2px(mContext, 5), i); mPaint.setStrokeWidth(2); } mCanvas.drawLine(outCircleX, outCircleY, inCircleX, intCircleY, mPaint); } currentPosition = 0; }
画数字的方法:
private void drawNum(Canvas canvas, String text, float textX, float textY) { mPaint.setTextSize(DensityUtil.dip2px(mContext, 12)); float textWidth = mPaint.measureText(text); canvas.drawText(text, textX - textWidth / 2, textY + DensityUtil.dip2px(mContext, 4), mPaint);//绘制数字并修正数字的位置 }
private void drawClockHand() { mCanvas.drawLine(mCenterX, mCenterY, calculateX(SecondHandLength, secondRadian + HALF_PI), calculateY(SecondHandLength, secondRadian + HALF_PI), mPaint); mPaint.setColor(Color.BLUE); mCanvas.drawLine(mCenterX, mCenterY, calculateX(MinuteHandLength, secondRadian / 60 + HALF_PI), calculateY(MinuteHandLength, secondRadian / 60 + HALF_PI), mPaint); mPaint.setColor(Color.RED); //注意,这里除以的是720不是3600,由于时针走一小格为12分钟,至关于分针走12格,秒针走720格 mCanvas.drawLine(mCenterX, mCenterY, calculateX(hourHandLength, secondRadian / 720 + HALF_PI), calculateY(hourHandLength, secondRadian / 720 + HALF_PI), mPaint); }
好啦,基本的轮廓已经画完:
咱们让它跑起来吧:
在Run方法里面不断地更新当前时间:
/** * 更新当前时间和刻度值 */ private void updateCurrentTime() { mFormat.format(new Date()); mHour = mFormat.getCalendar().get(Calendar.HOUR_OF_DAY); mMinute = mFormat.getCalendar().get(Calendar.MINUTE); mSecond = mFormat.getCalendar().get(Calendar.SECOND); int totalS = mHour * 3600 + mMinute * 60 + mSecond; secondRadian = -totalS * intervalRadian; }
咱们每秒刷新一次。
@Override public void run() { while (mIsDrawing && sizeChanged) { try { updateCurrentTime(); long drawBefore = System.currentTimeMillis(); draw(); long drawTime = System.currentTimeMillis() - drawBefore; if (drawTime < 1000) { Thread.sleep(1000 - drawTime); } } catch (Exception e) { e.printStackTrace(); } } }附上定义的成员变量:
private SurfaceHolder mHolder; private Canvas mCanvas; private boolean mIsDrawing; private Paint mPaint; private int mRadius; private int currentPosition = 0;//记录目前所画第几个刻度点,从3点开始逆时针 private int mCenterX; private int mCenterY; private int hourHandLength; private int MinuteHandLength; private int SecondHandLength; private double secondRadian; private boolean sizeChanged; private int mMinute; private int mSecond; private int mHour; private SimpleDateFormat mFormat; public static final double intervalRadian = 2 * Math.PI / 60; private static final double HALF_PI = Math.PI / 2; private Context mContext;
附上项目源码地址:点击下载源码