前段时间看过郭霖大神公众号推送的一篇文章《一步步实现精美的钟表界面》,忍不住自己也去实现一番,然后去搜了一些钟表图片,发现有个挺不错的,然后就按照文章中的一些思路开始一步步实现这个荧光时钟。
效果图
![](https://img.haomeiwen.com/i247340/d0bf5156a25d922f.png)
1.第一步,创建自定义View继承View,实现构造方法,如下
public LightClock(Context context) {
super(context);
}
public LightClock(Context context, AttributeSet attrs) {
super(context, attrs);
obtainStyledAttrs(attrs);
}
2.第二步,添加必要属性,创建自定义资源文件
/**
* 最外层颜色
*/
private int outCircleColor;
/**
* 第二层颜色
*/
private int secondCircleColor;
/**
* 表头颜色
*/
private int clockColor;
/**
* 一刻钟的刻度的颜色
*/
private int quarterColor;
/**
* 其他刻度的颜色
*/
private int minuteColor;
/**
* 圆心小圆的颜色
*/
private int centerCircleColor;
/**
* 指针的颜色
*/
private int wiseColor;
/**
* 指针上正方形的颜色
*/
private int squareColorOfWise;
/**
* 指针上圆形颜色
*/
private int circleColorOfWise;
/**
* 指针上 三角形的颜色
*/
private int triangleColorOfWise;
/**
* 时钟半径
*/
private float radiusOfClock;
/**
* 中间小圆的半径
*/
private float radiusOfCenterCircle;
/**
* 时钟的中心点
*/
private float centerXofClock, centerYofClock;
/**
* 一刻钟的圆的半径
*/
private float radiusOfquarter;
/**
* 其他刻度的圆的半径
*/
private float radiusOfminute;
/**
* 正方形的宽度
*/
private float squareWidth;
/**
* 三角形高度,该三角形的高=2*底边
*/
private float triangleHeight;
/**
* 指针圆形半径
*/
private float radiusOfWiseCircle;
res目录下创建自定 attr.xml
<declare-styleable name="LightClock">
<attr name="outsideCircleColor" format="color"></attr>
<attr name="secondCircleColor" format="color"></attr>
<attr name="clockColor" format="color"></attr>
<attr name="quarterColor" format="color"></attr>
<attr name="minuteColor" format="color"></attr>
<attr name="centerCircleColor" format="color"></attr>
<attr name="wiseColor" format="color"></attr>
<attr name="squareColorOfWise" format="color"></attr>
<attr name="circleColorOfWise" format="color"></attr>
<attr name="triangleColorOfWise" format="color"></attr>
</declare-styleable>
构造方法中获取属性并且设置默认值,添加异常情况的处理(一旦出现异常,使用全部默认值)
public LightClock(Context context, AttributeSet attrs) {
super(context, attrs);
obtainStyledAttrs(attrs);
}
/**
* 获取样式,出现异常时取默认值
*
* @param attrs
*/
private void obtainStyledAttrs(AttributeSet attrs) {
TypedArray array;
try {
array = getContext().obtainStyledAttributes(attrs, R.styleable.LightClock);
outCircleColor = getColor(array, R.styleable.LightClock_outsideCircleColor, Color.rgb(187, 184, 183));//#bbb8b7
secondCircleColor = getColor(array, R.styleable.LightClock_secondCircleColor, Color.rgb(105, 104, 103));//#696867
clockColor = getColor(array, R.styleable.LightClock_clockColor, Color.rgb(0, 0, 0));//#000000
quarterColor = getColor(array, R.styleable.LightClock_quarterColor, Color.rgb(77, 255, 255));//4dffff
minuteColor = getColor(array, R.styleable.LightClock_minuteColor, Color.rgb(105, 104, 103));//#696867
centerCircleColor = getColor(array, R.styleable.LightClock_centerCircleColor, Color.rgb(255, 158, 11));//#ff9e0b
wiseColor = getColor(array, R.styleable.LightClock_wiseColor, Color.rgb(105, 104, 103));//#696867
squareColorOfWise = getColor(array, R.styleable.LightClock_squareColorOfWise, Color.rgb(0, 255, 0));//#00ff00
circleColorOfWise = getColor(array, R.styleable.LightClock_circleColorOfWise, Color.rgb(255, 54, 54));//#ff3636
triangleColorOfWise = getColor(array, R.styleable.LightClock_triangleColorOfWise, Color.rgb(255, 255, 49));//#ffff31
} catch (Exception e) {
outCircleColor=Color.rgb(187, 184, 183);
secondCircleColor = Color.rgb(105, 104, 103);//#696867
clockColor = Color.rgb(0, 0, 0);//#000000
quarterColor = Color.rgb(77, 255, 255);//4dffff
minuteColor = Color.rgb(105, 104, 103);//#696867
centerCircleColor =Color.rgb(255, 158, 11);//#ff9e0b
wiseColor = Color.rgb(105, 104, 103);//#696867
squareColorOfWise = Color.rgb(0, 255, 0);//#00ff00
circleColorOfWise =Color.rgb(255, 54, 54);//#ff3636
triangleColorOfWise = Color.rgb(255, 255, 49);//#ffff31
}
}
/**
* 获取相应颜色
*
* @param array
* @param colorId
* @param defaultColor
* @return
*/
public int getColor(TypedArray array, int colorId, int defaultColor) {
return array.getColor(colorId, defaultColor);
}
测量 view 长宽,确保 view长宽相等,需重写onMeasure方法,使得表盘始终只占用一个正方形的空间
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
if (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED || heightMeasureSpec == MeasureSpec.AT_MOST || heightMeasureSpec == MeasureSpec.UNSPECIFIED) {
try {
throw new NoDetermineSizeException("宽度高度至少有一个确定的值,不能同时为wrap_content");
} catch (NoDetermineSizeException e) {
e.printStackTrace();
}
} else {
// 获得该view真实的宽度和高度
int mRealWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
int mRealHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
Log.v("","mRealHeight="+mRealHeight);
//取最小值
int width=Math.min(mRealHeight,mRealWidth);
//设置长宽相等
setMeasuredDimension(width,width);
}
}
由于在onMeasure方法中设置了view 的长宽相等,需重写 onSizeChanged方法
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//设置时钟的半径为 view 边长的一半
radiusOfClock = (Math.min(w, h)) / 2;
//设置中心点
centerYofClock = radiusOfClock;
centerXofClock = radiusOfClock;
//整点刻度的圆半径为时钟半径的1/40
radiusOfquarter=0.025f*radiusOfClock;
//非整点刻度的圆半径为时钟半径的1/100
radiusOfminute=0.01f*radiusOfClock;
//时钟中心小圆的半径为时钟半径的3/100
radiusOfCenterCircle=0.03f*radiusOfClock;
//分针上的小圆半径为整点刻度小圆的1.6倍
radiusOfWiseCircle=1.6f*radiusOfquarter;
//时针上的正方形边长为时钟半径的1/20
squareWidth=0.05f*radiusOfClock;
//三角形高度=正方形边长
triangleHeight=squareWidth;
}
3.第三步,开始绘制时钟
所有绘制动作均在 ondraw 方法中执行,为了减少坐标计算量,利用 canvas的translate方法将画布坐标原点从(0,0)转移到时钟中心的坐标点
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//先锁定画布
canvas.save();
//将画布原点坐标从0,0转移到时钟中心的坐标点
canvas.translate(centerXofClock, centerYofClock);
drawTheOutCircle(canvas);
drawSecondCircle(canvas);
drawBoardCicle(canvas);
//绘制刻度
drawScale(canvas);
//绘制时分秒针
drawClockwise(canvas);
//绘制中心小圆
drawCenterCircle(canvas);
//还原画布位置
canvas.restore();
//每隔一秒刷新一次
postInvalidateDelayed(1000);
}
3.1 绘制外圈圆
/**
* 画最外圈
* @param canvas
*/
private void drawTheOutCircle(Canvas canvas) {
Paint paint = getPaint(outCircleColor);
canvas.drawCircle(0, 0, radiusOfClock, paint);
}
/**
* 画第二个圈
* @param canvas
*/
private void drawSecondCircle(Canvas canvas){
Paint paint = getPaint(secondCircleColor);
canvas.drawCircle(0, 0, radiusOfClock*0.95f, paint);
}
/**
* 画中间时钟的圈
* @param canvas
*/
private void drawBoardCicle(Canvas canvas){
Paint paint = getPaint(clockColor);
canvas.drawCircle(0, 0, radiusOfClock*0.92f, paint);
}
效果
![](https://img.haomeiwen.com/i247340/5ea55054a0193a17.png)
3.2绘制刻度
绘制的时候我们希望能够直接在X轴或者Y轴上绘制线条,但当前的x,y相对于原点来说都是水平,垂直的,那么我们就可以想到将坐标系每次旋转6°进行绘制,一共旋转60次,并且每次都是在x轴或者y轴上绘制.这样的话就能减少大量的坐标换算量(PS:当时笔者居然想着要用到高中知识利用角度去换算 x和y值,简直奔溃[捂脸])
/**
* 画刻度
* @param canvas
*/
private void drawScale(Canvas canvas){
//共有60个刻度,循环画60次,每个刻度占6度
for(int i=0;i<60;i++){
//整点
if(i%5==0){
Paint scalePaint =getPaint(quarterColor);
canvas.drawCircle(0,-0.8f*radiusOfClock,radiusOfquarter,scalePaint);
}else{
Paint scalePaint =getPaint(minuteColor);
canvas.drawCircle(0,-0.8f*radiusOfClock,radiusOfminute,scalePaint);
}
//每画一个刻度,将当前画布旋转6度
canvas.rotate(6);
}
}
效果
![](https://img.haomeiwen.com/i247340/5430b32b18e221f8.png)
3.3 绘制时分秒针
/**时针占半径的比例*/
public static final float HOUR_WISE_PERCENT=0.5F;
/**分针占半径的比例*/
public static final float MINUTE_WISE_PERCENT=0.65F;
/**秒针占半径的比例*/
public static final float SECOND_WISE_PERCENT=0.8F;
/**
* 用于绘制时分秒针
* 绘制每一根针前都需要调用 canvas.save()去锁定当前画布,再根据时间旋转角度,画完需要调用 canvas.restore()还原画布
* @param canvas
*/
private void drawClockwise(Canvas canvas) {
Paint squarePaint = getPaint(squareColorOfWise);
Paint circlePaint = getPaint(circleColorOfWise);
Paint trianglePaint=getPaint(triangleColorOfWise);
Paint wisePaint = getPaint(wiseColor);
//指针的宽度
wisePaint.setStrokeWidth(4f);
//获取当前时间的时分秒
Calendar calendar = Calendar.getInstance();
//计算各条针应该旋转的角度
int hour = calendar.get(Calendar.HOUR_OF_DAY);
int minute = calendar.get(Calendar.MINUTE);
int second = calendar.get(Calendar.SECOND);
if (timeChangeListener != null) {
timeChangeListener.onTimeChange(hour, minute, second);
}
int angleHour = (hour % 12) * 360 / 12;//时针转过的角度
int angleMinute = minute * 360 / 60;//分针转过的角度
int angleSecond = second * 360 / 60;//秒针转过的角度
//绘制时针
canvas.save();
canvas.rotate(angleHour);
canvas.drawLine(0, -HOUR_WISE_PERCENT*radiusOfClock, 0, 0, wisePaint);
//绘制时针尾的正方形
canvas.drawRect(-squareWidth,-HOUR_WISE_PERCENT*radiusOfClock-squareWidth,squareWidth,squareWidth-HOUR_WISE_PERCENT*radiusOfClock,squarePaint);
canvas.restore();
//绘制分针
canvas.save();
canvas.rotate(angleMinute);
canvas.drawLine(0, -MINUTE_WISE_PERCENT*radiusOfClock, 0, 0, wisePaint);
//绘制分针尾的小圆
canvas.drawCircle(0, -MINUTE_WISE_PERCENT*radiusOfClock,radiusOfWiseCircle,circlePaint);
canvas.restore();
//绘制秒针
canvas.save();
canvas.rotate(angleSecond);
canvas.drawLine(0, - SECOND_WISE_PERCENT*radiusOfClock, 0, 0, wisePaint);
//设置三角形的路径
Path trianglePath= new Path();
//设置起点
trianglePath.moveTo(0,- SECOND_WISE_PERCENT*radiusOfClock-triangleHeight);
//从起点划线移到第二顶点
trianglePath.lineTo(-triangleHeight,- SECOND_WISE_PERCENT*radiusOfClock+triangleHeight);
//从第二个顶点移到第三个顶点
trianglePath.lineTo(triangleHeight,- SECOND_WISE_PERCENT*radiusOfClock+triangleHeight);
//绘制三角形
canvas.drawPath(trianglePath,trianglePaint);
canvas.restore();
}
效果
![](https://img.haomeiwen.com/i247340/d0bf5156a25d922f.png)
完整代码请浏览以下链接
https://github.com/yuwenque/ClockSample
网友评论