Android自定义实现绘制时钟表盘
首先看下效果图:

实现步骤:
绘制表盘[刻度,数字]
绘制指针
让指针走起来~
具体如下:
绘制表盘:
首先需要计算出刻度的起点和终点坐标值,这里我们通过构建两个半径不同的同心圆,大圆半径减小圆半径,就可以得到一条刻度,只用改变角度,就可以获取所有刻度:

/*** 通过改变角度值,获取不同角度方向的外圆一点到圆心连线过内圆一点的路径坐标集合* @param x0 圆心x* @param y0 圆心y* @param outRadius 外圆半径* @param innerRadius 内圆半径* @param angle 角度* @return 返回*/private float[] getDialPaths(int x0,int y0,int outRadius,int innerRadius,int angle){float[] paths = new float[4];paths[0] = (float) (x0 + outRadius * Math.cos(angle * Math.PI / 180));paths[1] = (float) (y0 + outRadius * Math.sin(angle * Math.PI / 180));paths[2] = (float) (x0 + innerRadius * Math.cos(angle * Math.PI / 180));paths[3] = (float) (y0 + innerRadius * Math.sin(angle * Math.PI / 180));return paths;}
秒针刻度间隔360/60 = 6 度,循环绘制60次,每一次角度加6,就可以了,绘制代码如下:
for (int i = 0; i < 60 ; i++) {if (i % 5 == 0){//获取刻度路径float[] dialKdPaths = getDialPaths(halfMinLength, halfMinLength, halfMinLength - 8, halfMinLength * 5 / 6, -i * 6);canvas.drawLines(dialKdPaths,paintKd30);float[] dialPathsStr = getDialPaths(halfMinLength, halfMinLength, halfMinLength - 8, halfMinLength * 3 / 4, -i * 6);canvas.drawText(strKedu[i/5],dialPathsStr[2] - 16,dialPathsStr[3] + 14,paintKd30Text);continue;}float[] dialKdPaths = getDialPaths(halfMinLength, halfMinLength, halfMinLength - 8, halfMinLength * 7 / 8, -i * 6);canvas.drawLines(dialKdPaths,paintKdSecond);}
绘制指针和旋转指针
这里的重点在于对指针旋转的理解:

通过上图可以看到,我们通过旋转画布,然后绘制指针,最后恢复画布,从而改变了指针的指向,具体操作过程是:
保存已经绘制画面
以一定角度旋转画布
绘制指针
恢复画布角度
代码如下:以时针绘制为例
//时针绘制canvas.save(); //保存之前内容canvas.rotate(angleHour,halfMinLength,halfMinLength); //旋转的是画布,从而得到指针旋转的效果canvas.drawLine(halfMinLength,halfMinLength,halfMinLength,halfMinLength*3/4,paintHour);canvas.restore(); //恢复
让时间走起来
通过实时的计算时针,分针,秒针的角度,然后通知重新绘制画面,我们就看到时间在走动。
/*** 更新时分秒针的角度,开始绘制*/public void startRun(){new Thread(new Runnable() {@Overridepublic void run() {while (drawable){try {Thread.sleep(1000); // 睡1supdataAngleSecond(); //更新秒针角度updataAngleMinute(); //更新分针角度updataAngleHour(); //更新时针角度postInvalidate(); //重新绘制} catch (InterruptedException e) {e.printStackTrace();}}}}).start();}
完整代码如下:
public class DialView extends View {private boolean drawable = true; //是否可以绘制private int halfMinLength; //最小宽/高的一半长度private Paint paintKd30; //时针刻度线画笔private Paint paintKd30Text; // 时针数字画笔private Paint paintKdSecond; //秒针刻度线画笔private Paint paintHour; //时针画笔private Paint paintCircleBar;//指针圆心画笔private Paint paintMinute; //分针画笔private Paint paintSecond; //秒针画笔private float angleHour; //时针旋转角度private float angleMinute; //分针旋转角度private float angleSecond; //秒针旋转角度private int cuurSecond; //当前秒private int cuurMinute; //当前分private int cuurHour; //当前时private Calendar mCalendar;private boolean isMorning = true; //上午/下午private String[] strKedu = {"3","2","1","12","11","10","9","8","7","6","5","4"};public DialView(Context context) {this(context,null);}public DialView(Context context, AttributeSet attrs) {this(context, attrs,-1);}public DialView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);initPaint(); //初始化画笔initTime(); //初始化时间}protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int width = MeasureSpec.getSize(widthMeasureSpec);int height = MeasureSpec.getSize(heightMeasureSpec);halfMinLength = Math.min(width,height) / 2;System.out.println(halfMinLength);}protected void onDraw(Canvas canvas) {super.onDraw(canvas);//表盘刻度绘制for (int i = 0; i < 60 ; i++) {if (i % 5 == 0){//获取刻度路径float[] dialKdPaths = getDialPaths(halfMinLength, halfMinLength, halfMinLength - 8, halfMinLength * 5 / 6, -i * 6);canvas.drawLines(dialKdPaths,paintKd30);float[] dialPathsStr = getDialPaths(halfMinLength, halfMinLength, halfMinLength - 8, halfMinLength * 3 / 4, -i * 6);canvas.drawText(strKedu[i/5],dialPathsStr[2] - 16,dialPathsStr[3] + 14,paintKd30Text);continue;}float[] dialKdPaths = getDialPaths(halfMinLength, halfMinLength, halfMinLength - 8, halfMinLength * 7 / 8, -i * 6);canvas.drawLines(dialKdPaths,paintKdSecond);}//指针绘制//时针绘制canvas.save(); //保存之前内容canvas.rotate(angleHour,halfMinLength,halfMinLength); //旋转的是画布,从而得到指针旋转的效果canvas.drawLine(halfMinLength,halfMinLength,halfMinLength,halfMinLength*3/4,paintHour);canvas.restore(); //恢复//绘制分针canvas.save();canvas.rotate(angleMinute,halfMinLength,halfMinLength); //旋转的是画布,从而得到指针旋转的效果canvas.drawLine(halfMinLength,halfMinLength,halfMinLength,halfMinLength/2,paintMinute);paintCircleBar.setColor(Color.rgb(75,75,75));paintCircleBar.setShadowLayer(4,4,8,Color.argb(70,40,40,40));canvas.drawCircle(halfMinLength,halfMinLength,24,paintCircleBar);canvas.restore();//绘制秒针canvas.save();canvas.rotate(angleSecond,halfMinLength,halfMinLength); //旋转的是画布,从而得到指针旋转的效果canvas.drawLine(halfMinLength,halfMinLength + 40,halfMinLength,halfMinLength / 4 - 20,paintSecond);paintCircleBar.setColor(Color.rgb(178,34,34));paintCircleBar.setShadowLayer(4,4,8,Color.argb(50,80,0,0));canvas.drawCircle(halfMinLength,halfMinLength,12,paintCircleBar);canvas.restore();}/*** 初始化时,分,秒*/private void initTime() {mCalendar = Calendar.getInstance();cuurHour = mCalendar.get(Calendar.HOUR_OF_DAY);cuurMinute = mCalendar.get(Calendar.MINUTE);cuurSecond = mCalendar.get(Calendar.SECOND);if (cuurHour >= 12){cuurHour = cuurHour - 12;isMorning = false;}else{isMorning = true;}angleSecond = cuurSecond * 6f;angleMinute = cuurMinute * 6f;angleHour = cuurHour * 6f * 5f;}/*** 更新时分秒针的角度,开始绘制*/public void startRun(){new Thread(new Runnable() {public void run() {while (drawable){try {Thread.sleep(1000); // 睡1supdataAngleSecond(); //更新秒针角度updataAngleMinute(); //更新分针角度updataAngleHour(); //更新时针角度postInvalidate(); //重新绘制} catch (InterruptedException e) {e.printStackTrace();}}}}).start();}private void updataAngleHour() {//更新时针角度angleHour = angleHour + (30f/3600);if (angleHour >= 360){angleHour = 0;cuurHour = 0;}}private void updataAngleMinute() {//更新分针角度angleMinute = angleMinute + 0.1f;if (angleMinute >= 360){angleMinute = 0;cuurMinute = 0;cuurHour += 1;}}private void updataAngleSecond() {//更新秒针角度angleSecond = angleSecond + 6;cuurSecond += 1;if (angleSecond >= 360){angleSecond = 0;cuurSecond = 0;cuurMinute += 1;//一分钟同步一次本地时间mCalendar = Calendar.getInstance();cuurHour = mCalendar.get(Calendar.HOUR_OF_DAY);cuurMinute = mCalendar.get(Calendar.MINUTE);cuurSecond = mCalendar.get(Calendar.SECOND);if (cuurHour >= 12){cuurHour = cuurHour - 12;isMorning = false;}else{isMorning = true;}angleSecond = cuurSecond * 6f;angleMinute = cuurMinute * 6f;angleHour = cuurHour * 6f * 5f;}}/*** 停止绘制*/public void stopDrawing(){drawable = false;}/*** 通过改变角度值,获取不同角度方向的外圆一点到圆心连线过内圆一点的路径坐标集合* @param x0 圆心x* @param y0 圆心y* @param outRadius 外圆半径* @param innerRadius 内圆半径* @param angle 角度* @return 返回*/private float[] getDialPaths(int x0,int y0,int outRadius,int innerRadius,int angle){float[] paths = new float[4];paths[0] = (float) (x0 + outRadius * Math.cos(angle * Math.PI / 180));paths[1] = (float) (y0 + outRadius * Math.sin(angle * Math.PI / 180));paths[2] = (float) (x0 + innerRadius * Math.cos(angle * Math.PI / 180));paths[3] = (float) (y0 + innerRadius * Math.sin(angle * Math.PI / 180));return paths;}/*** 初始化画笔参数*/private void initPaint() {paintKd30 = new Paint();paintKd30.setStrokeWidth(8);paintKd30.setColor(Color.rgb(75,75,75));paintKd30.setAntiAlias(true);paintKd30.setDither(true);paintKd30.setStrokeCap(Paint.Cap.ROUND);paintKd30Text = new Paint();paintKd30Text.setTextAlign(Paint.Align.LEFT); //左对齐paintKd30Text.setStrokeWidth(6); //设置宽度paintKd30Text.setTextSize(40); //文字大小paintKd30Text.setTypeface(Typeface.DEFAULT_BOLD); //加粗paintKd30Text.setColor(Color.rgb(75,75,75)); //画笔颜色paintKd30Text.setAntiAlias(true); //抗锯齿paintKd30Text.setDither(true); //抖动paintKd30Text.setStrokeCap(Paint.Cap.ROUND); //笔尖圆角paintKd30Text.setShadowLayer(4,2,4,Color.argb(60,90,90,90)); //阴影paintKdSecond = new Paint();paintKdSecond.setStrokeWidth(6);paintKdSecond.setColor(Color.rgb(75,75,75));paintKdSecond.setAntiAlias(true);paintKdSecond.setDither(true);paintKdSecond.setStrokeCap(Paint.Cap.ROUND);paintKdSecond.setShadowLayer(4,5,10,Color.argb(50,80,80,80));paintHour = new Paint();paintHour.setStrokeWidth(30);paintHour.setColor(Color.rgb(75,75,75));paintHour.setAntiAlias(true);paintHour.setDither(true);paintHour.setStrokeCap(Paint.Cap.ROUND);paintHour.setShadowLayer(4,5,10,Color.argb(50,80,80,80));paintCircleBar = new Paint();paintCircleBar.setStrokeWidth(6);// paintCircleBar.setColor(Color.rgb(178,34,34));paintCircleBar.setAntiAlias(true);paintCircleBar.setDither(true);paintCircleBar.setStrokeCap(Paint.Cap.ROUND);// paintCircleBar.setShadowLayer(4,5,10,Color.argb(100,80,80,80));paintMinute = new Paint();paintMinute.setStrokeWidth(30);paintMinute.setColor(Color.rgb(75,75,75));paintMinute.setAntiAlias(true);paintMinute.setDither(true);paintMinute.setStrokeCap(Paint.Cap.ROUND);paintMinute.setShadowLayer(4,5,10,Color.rgb(80,80,80));paintSecond = new Paint();paintSecond.setStrokeWidth(6);paintSecond.setColor(Color.rgb(180,30,30));paintSecond.setAntiAlias(true);paintSecond.setDither(true);paintSecond.setStrokeCap(Paint.Cap.ROUND);paintSecond.setShadowLayer(4,2,10,Color.argb(100,90,90,90));}}
代码中有比较详细的注释。
评论
