前言:实现完前篇文章中的荧光时钟后,突然想起大学期间做数字电路实验经常见到的LED数字显示屏,数字电路的课设也用 LED 数字结合简单的电路做了一个简单的老虎机游戏,甚是怀念。好吧!开始实现一个 简单的LED 数字时钟!
LED 数字时钟效果图
1. 如何实现LED数字
LED 数字无非就是由七根线组成,只要根据数字0-9线条的组成特性,画出对应的线条即可得到相应数字。
基本思路:画完一个数字后,将画布移动一定的偏移量,继续画下一个数字,依次类推。由于画数字的动作其实是重复的,我们可以封装一个工具类NumPaintUtil来画数字。
每个LED数字自身的坐标系示意图参考如下,每根线的坐标请根据代码意会
public class NumPaintUtil {
private Canvas canvas;// view 的画布
private float lineWidth;//线的长度
private int lineColor;
private float padding=10;//竖直间距,默认为10
/**
*
* @param canvas 画布
* @param lineWidth 线长
* @param lineColor 线的颜色
*/
public NumPaintUtil(Canvas canvas,float lineWidth,int lineColor){
this.canvas=canvas;
this.lineWidth=lineWidth;
this.lineColor=lineColor;
}
/**
*
* @param lineWidth 线长
* @param lineColor 线的颜色
*/
public NumPaintUtil(float lineWidth,int lineColor){
this.lineWidth=lineWidth;
this.lineColor=lineColor;
}
public Canvas getCanvas() {
return canvas;
}
/**
*
* 调用drawNumber之前需设置画布对象
* @param canvas
*/
public void setCanvas(Canvas canvas) {
this.canvas = canvas;
}
/**
*
* 根据0-9的特性绘制线条从而实现对应的数字
* 使用前,需初始化设置canvas,并且请将 canvas 移动到合适的位置
* @param num 数字
*/
public void drawNumber(int num){
if(canvas==null){
try{
throw new CanvasNullPointException("canvas is null,please init canvas");
}catch (CanvasNullPointException e){
e.printStackTrace();
}
return;
}
switch (num) {
case 0:
/**
* 去掉中间线即可
* ——
* | |
* | |
* ——
*/
drawTopLeftLine();
drawTopLine();
drawBottomLine();
drawTopRightLine();
drawBottomLeftLine();
drawBottomRightLine();
break;
case 1:
/**
* 只画右上和右下的线
*
* |
* |
*
*/
drawTopRightLine();
drawBottomRightLine();
break;
case 2:
/**
* 去掉左上和右下
*
* ——
* |
* ——
* |
* ——
*
*/
drawCenterLine();
drawTopLine();
drawBottomLine();
drawTopRightLine();
drawBottomLeftLine();
break;
case 3:
/**
* 去掉左上和左下
*
* ——
* |
* ——
* |
* ——
*
*/
drawCenterLine();
drawTopLine();
drawBottomLine();
drawTopRightLine();
drawBottomRightLine();
break;
case 4:
/**
* 去掉顶部、底部和左下
*
*
* | |
* ——
* |
*
*/
drawCenterLine();
drawTopLeftLine();
drawTopRightLine();
drawBottomRightLine();
break;
case 5:
/**
* 去掉右上和左下
*
* ——
* |
* ——
* |
* ——
*
*/
drawCenterLine();
drawTopLeftLine();
drawTopLine();
drawBottomLine();
drawBottomRightLine();
break;
case 6:
/**
* 去掉右上
*
* ——
* |
* ——
* | |
* ——
*
*/
drawCenterLine();
drawTopLine();
drawBottomLine();
drawTopLeftLine();
drawBottomLeftLine();
drawBottomRightLine();
break;
case 7:
/**
* ——
* |
*
* |
*/
drawTopLine();
drawTopRightLine();
drawBottomRightLine();
break;
case 8:
/**
* 全保留
* __
* | |
* ——
* | |
* ——
*/
drawCenterLine();
drawTopLeftLine();
drawTopLine();
drawBottomLine();
drawTopRightLine();
drawBottomLeftLine();
drawBottomRightLine();
break;
case 9:
/**
* 去掉左下
* __
* | |
* ——
* |
* ——
*/
drawCenterLine();
drawTopLeftLine();
drawTopLine();
drawBottomLine();
drawTopRightLine();
drawBottomRightLine();
break;
}
}
public class CanvasNullPointException extends NullPointerException{
public CanvasNullPointException(String msg){
super(msg);
}
}
/**
* 画中间线
*
*/
private void drawCenterLine() {
Paint numPaint = getPaint(lineColor);
canvas.drawLine(-lineWidth / 2, 0, lineWidth / 2, 0, numPaint);
}
/**
* 画top 线
*
*/
private void drawTopLine() {
Paint numPaint = getPaint(lineColor);
canvas.drawLine(-lineWidth / 2, -lineWidth - padding, lineWidth / 2, -lineWidth - padding, numPaint);
}
/**
* 画底部的线
*
*/
private void drawBottomLine() {
Paint numPaint = getPaint(lineColor);
canvas.drawLine(-lineWidth / 2, lineWidth + padding, lineWidth / 2, lineWidth + padding, numPaint);
}
/**
* 画左上的线
*
*/
private void drawTopLeftLine() {
Paint numPaint = getPaint(lineColor);
canvas.drawLine(-lineWidth / 2, -padding/2, -lineWidth / 2, -padding/2 - lineWidth, numPaint);
}
/**
* 画右上的线
*
*/
private void drawTopRightLine() {
Paint numPaint = getPaint(lineColor);
canvas.drawLine(lineWidth / 2, -padding/2, lineWidth / 2, -padding/2 - lineWidth, numPaint);
}
/**
* 画左下
*
*/
private void drawBottomLeftLine() {
Paint numPaint = getPaint(lineColor);
canvas.drawLine(-lineWidth / 2, padding/2, -lineWidth / 2, padding/2 + lineWidth, numPaint);
}
/**
* 画右下
*
*/
private void drawBottomRightLine() {
Paint numPaint = getPaint(lineColor);
canvas.drawLine(lineWidth / 2, padding/2, lineWidth / 2, padding/2 + lineWidth, numPaint);
}
/**
* 获得对应颜色的画笔
*
* @param color
* @return
*/
public Paint getPaint(int color) {
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(color);
paint.setStrokeWidth(5f);
paint.setDither(true);
return paint;
}
}
2.使用 NumPaintUtil 来逐步绘制LED时钟
2.1 第一步,自定义NumClock继承 View,实现以下构造方法
public NumClock(Context context) {
super(context);
}
public NumClock(Context context, AttributeSet attrs) {
super(context, attrs);
obtainStyledAttrs(attrs);
}
2.2 第二步,添加必要属性
//线的颜色
private int lineColor;
//中间两小点的颜色
private int pointColor;
//是否展示秒数
private boolean showSeconds;
//控件真实长宽
private int mRealWidth, mRealHeight;
private float centerX, centerY;
private float lineWidth;//线的长度
private float padding;//数字间的间距
private float centerPadding;//时与分的间距
2.3 自定义 attr.xml 文件,增加以下属性,并获取样式
/**
* 获取样式,假如出现异常时取默认值
*
* @param attrs
*/
private void obtainStyledAttrs(AttributeSet attrs) {
TypedArray array;
try {
array = getContext().obtainStyledAttributes(attrs, R.styleable.NumClock);
lineColor = array.getColor(R.styleable.NumClock_lineColor, Color.parseColor("#ffaacc"));
showSeconds = array.getBoolean(R.styleable.NumClock_showSecond, false);
pointColor = array.getColor(R.styleable.NumClock_pointColor, Color.parseColor("#ffaacc"));
} catch (Exception e) {
lineColor = Color.parseColor("#ffaacc");
pointColor = Color.parseColor("#ffaacc");
showSeconds = false;
}
}
2.4 测量 view 的真实长宽,设置 view 的比例
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 获得该view真实的宽度和高度
mRealWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
mRealHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
//取最小值
int miniValue = Math.min(mRealHeight, mRealWidth);
//设置长宽比为3:1
int height = miniValue;
int width = miniValue * 3;
//更改 view 的长宽
setMeasuredDimension(width, height);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//TODO 设置各个参数
centerX = w / 2;
centerY = h / 2;
mRealWidth = w;
mRealHeight = h;
//设置每一根线的长度为 view 宽度的十分一
lineWidth = w / 10;
//数字间的间距
padding = (mRealWidth - 4 * lineWidth) / 8f;
//时与分之间的间距
centerPadding = 2 * padding;
//由于 onDraw 调用比较频繁,故不在 onDraw 中实例化
numPaintUtil = new NumPaintUtil(lineWidth, lineColor);
}
2.5 开始绘制
PS:以下代码中调用了多次 canvas的 translate 平移方法,请先了解translate方法的机制及用途(这是自定义view 的基础知识),本文不作阐述。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//需在此设置numPaintUtil的 canvas 属性
numPaintUtil.setCanvas(canvas);
//获取当前的时分秒
Calendar calendar = Calendar.getInstance();
int hour = calendar.get(Calendar.HOUR_OF_DAY);
int minute = calendar.get(Calendar.MINUTE);
//锁定画布
canvas.save();
//为了减少坐标计算量,将坐标原点从(0,0)移动到 view 的中心点
canvas.translate(centerX, centerY);
//绘制时
drawHour(canvas, hour);
//是否闪烁点
if (isShow) {
drawPointBtHourNMinuter(canvas);
}
//更改标志位
isShow = !isShow;
//绘制分
drawMinute(canvas, minute);
//是否显示秒数
if (showSeconds) {
int second = calendar.get(Calendar.SECOND);
Paint textPaint = getPaint(lineColor);
textPaint.setTextSize(25f);
//绘制秒数
if (second < 10) {
canvas.drawText("0" + second, lineWidth / 2 + padding / 2, lineWidth, textPaint);
} else {
canvas.drawText(second + "", lineWidth / 2 + padding / 2, lineWidth, textPaint);
}
}
canvas.restore();
//每隔一秒刷新一次
postInvalidateDelayed(1000);
}
/**
* 绘制 时
*
* @param canvas 画布
* @param hour 时
*/
public void drawHour(Canvas canvas, int hour) {
int tenOfHour = hour / 10;//十位
int oneOfHour = hour % 10;//个位
//计算可得第一个数字的中心点 与 此时 canvas 的原点距离为 lineWidth+lineWidth/2 + padding + centerPadding / 2
float newCenterX = -(lineWidth+lineWidth/2 + padding + centerPadding / 2);
//将画布往左平移 newCenterX 为负值
canvas.translate(newCenterX, 0);
//开始绘制「时」中的十位
numPaintUtil.drawNumber(tenOfHour);
//计算可得第二个数字中心点与第一个数字的中心点(即此时 canvas 的原点)距离为lineWidth + padding ,为正值
//所以将画布在上一次偏移量的基础上再往右平移 lineWidth + padding
canvas.translate(lineWidth + padding, 0);
//绘制「时」中的个位
numPaintUtil.drawNumber(oneOfHour);
}
/**
* 绘制时与分之间闪烁的两个小点
*
* @param canvas
*/
public void drawPointBtHourNMinuter(Canvas canvas) {
Paint pointPaint = getPaint(pointColor);
//计算可得其坐标值
canvas.drawCircle(lineWidth / 2 + centerPadding / 2, lineWidth / 2, 5, pointPaint);
canvas.drawCircle(lineWidth / 2 + centerPadding / 2, -lineWidth / 2, 5, pointPaint);
}
/**
* 绘制 分
*
* @param canvas 画布
* @param minute 分钟
*/
public void drawMinute(Canvas canvas, int minute) {
int tenOfMinute = minute / 10;//十位
int oneOfMinuter = minute % 10;//个位
//第三个数字的中心点与第二个数字的中心点(即此时 canvas 的原点)的距离 为lineWidth + centerPadding,为正值
//将画布往右平移 lineWidth + centerPadding
canvas.translate(lineWidth + centerPadding, 0);
//绘制分钟的十位
numPaintUtil.drawNumber(tenOfMinute);
//第四个数字的中心点与第三个数字的中心点(即此时 canvas 的原点)的距离为lineWidth + padding,为正值
//将画布往右平移lineWidth + padding
canvas.translate(lineWidth + padding, 0);
//绘制分钟的个位
numPaintUtil.drawNumber(oneOfMinuter);
}
/**
* 获得对应颜色的画笔
*
* @param color 颜色
* @return
*/
public Paint getPaint(int color) {
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(color);
paint.setStrokeWidth(5f);
paint.setDither(true);
return paint;
}
源码请浏览https://github.com/yuwenque/ClockSample