手痒,撸个华为时钟玩玩,顺便强化下自定义view的技能,先把效果图和源码地址贴出来,有空再讲解实现思路。
华为时钟常规模式:
华为时钟深色模式:
高仿华为时钟效果图:
代码实现逻辑如下:
public class HwClock extends View {
// 默认高度
public final int defaultHeight = SizeUtils.dp2px(getContext(), 300);
// 默认宽度
public final int defaultWidth = SizeUtils.dp2px(getContext(), 300);
// 时钟距离时钟View的边距
public final int outMargin = SizeUtils.dp2px(getContext(), 20);
// 时钟数字距离时钟刻度的边距
public final int inMargin = SizeUtils.dp2px(getContext(), 15);
// 时针刻度的长度
public final int hourScaleLength = SizeUtils.dp2px(getContext(), 12);
// 秒针刻度的长度
public final int secondScaleLength = SizeUtils.dp2px(getContext(), 6);
// 内指针距离指针的边距
public final int inHandMargin = SizeUtils.dp2px(getContext(), 1);
// 时钟数字的大小
public final int timeTextSize = 40;
// 是否是深色模式
public boolean isDarkMode;
// View半径
public int viewRadius;
// 时钟半径
public int clockRadius;
// 刻度画笔
private Paint scalePaint;
// 时钟背景画笔
private Paint clockBgPaint;
// 画时钟数字画笔
private Paint timeTextPaint;
// 时针画笔
private Paint hourHandPaint;
// 时针画笔2
private Paint hourHandPaint2;
// 分针画笔
private Paint minuteHandPaint;
// 分针画笔2
private Paint minuteHandPaint2;
// 秒针画笔
private Paint secondHandPaint;
// 秒针画笔2
private Paint secondHandPaint2;
public HwClock(Context context) {
this(context, null);
}
public HwClock(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public HwClock(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initPaints();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 用下而上控制绘制层级
drawBackground(canvas);
drawHourHand(canvas);
drawMinuteHand(canvas);
drawSecondHand(canvas);
// 延迟一秒绘制一次,以此往复
postInvalidateDelayed(1000);
}
/**
* 设置时钟深色模式
*/
public void setDarkMode(boolean isDark) {
this.isDarkMode = isDark;
initPaints();
invalidate();
}
/**
* 初始化画笔
*/
private void initPaints() {
scalePaint = new Paint();
// 设置笔帽为椭圆
scalePaint.setStrokeCap(Paint.Cap.ROUND);
// 抗锯齿
scalePaint.setAntiAlias(true);
scalePaint.setStrokeWidth(3);
clockBgPaint = new Paint();
clockBgPaint.setAntiAlias(true);
clockBgPaint.setColor(Color.BLACK);
timeTextPaint = new Paint();
timeTextPaint.setAntiAlias(true);
timeTextPaint.setStrokeCap(Paint.Cap.ROUND);
timeTextPaint.setTextSize(timeTextSize);
hourHandPaint = new Paint();
hourHandPaint.setAntiAlias(true);
hourHandPaint.setStrokeCap(Paint.Cap.ROUND);
hourHandPaint.setStrokeWidth(25);
hourHandPaint2 = new Paint();
hourHandPaint2.setAntiAlias(true);
hourHandPaint2.setStrokeCap(Paint.Cap.ROUND);
hourHandPaint2.setStrokeWidth(10);
minuteHandPaint = new Paint();
minuteHandPaint.setAntiAlias(true);
minuteHandPaint.setStrokeCap(Paint.Cap.ROUND);
minuteHandPaint.setStrokeWidth(20);
minuteHandPaint2 = new Paint();
minuteHandPaint2.setAntiAlias(true);
minuteHandPaint2.setStrokeCap(Paint.Cap.ROUND);
minuteHandPaint2.setStrokeWidth(8);
secondHandPaint = new Paint();
secondHandPaint.setAntiAlias(true);
secondHandPaint.setStrokeCap(Paint.Cap.ROUND);
secondHandPaint.setColor(Color.RED);
secondHandPaint.setStrokeWidth(5);
secondHandPaint2 = new Paint();
secondHandPaint2.setAntiAlias(true);
if (isDarkMode) {
timeTextPaint.setColor(Color.WHITE);
hourHandPaint.setColor(Color.WHITE);
hourHandPaint2.setColor(Color.BLACK);
minuteHandPaint.setColor(Color.WHITE);
minuteHandPaint2.setColor(Color.BLACK);
} else {
timeTextPaint.setColor(Color.BLACK);
hourHandPaint.setColor(Color.BLACK);
hourHandPaint2.setColor(Color.WHITE);
minuteHandPaint.setColor(Color.BLACK);
minuteHandPaint2.setColor(Color.WHITE);
}
}
/**
* 画背景
*/
private void drawBackground(Canvas canvas) {
if (isDarkMode) {
canvas.drawCircle(viewRadius, viewRadius, clockRadius + SizeUtils.dp2px(getContext(), 5), clockBgPaint);
}
drawClockScale(canvas);
drawTimeText(canvas);
}
/**
* 画时钟刻度
*/
private void drawClockScale(Canvas canvas) {
canvas.save();
for (int i = 0; i < 60; i++) {
if (i % 5 == 0) {
if (isDarkMode) {
scalePaint.setColor(Color.WHITE);
} else {
scalePaint.setColor(Color.BLACK);
}
// 时针刻度绘制
canvas.drawLine(viewRadius, outMargin, viewRadius, outMargin + hourScaleLength, scalePaint);
} else {
scalePaint.setColor(Color.GRAY);
// 秒针刻度绘制
canvas.drawLine(viewRadius, outMargin, viewRadius, outMargin + secondScaleLength, scalePaint);
}
canvas.rotate(6, viewRadius, viewRadius);
}
canvas.restore();
}
/**
* 画时钟数字
*/
private void drawTimeText(Canvas canvas) {
String timeText;
for (int i = 0; i < 12; i++) {
if (i == 0) {
timeText = "12";
} else {
timeText = i + "";
}
// 三角函数边长C
int lengthC = viewRadius - (outMargin + hourScaleLength + inMargin);
double[] results = calculateTimeTextPosition(lengthC, i * 30, timeText);
canvas.drawText(timeText, Float.parseFloat(results[0] + ""), Float.parseFloat(results[1] + ""), timeTextPaint);
}
}
/**
* 计算时钟数字坐标
*
* @param lengthC 三角函数边长C
* @param degrees 夹角
* @param timeText 时间数字
* @return int[0] = x坐标,int[2] = y坐标
*/
private double[] calculateTimeTextPosition(int lengthC, int degrees, String timeText) {
// 获取文字宽高
Rect timeTextRect = new Rect();
timeTextPaint.getTextBounds(timeText, 0, timeText.length(), timeTextRect);
int timeTextWidth = timeTextRect.width();
int timeTextHeight = timeTextRect.height();
// 利用三角函数计算文字坐标
double[] results = new double[2];
// x轴坐标 = sin*lengthC+view半径-文字一半宽度
results[0] = Math.sin(Math.PI / 180 * degrees) * lengthC + viewRadius - timeTextWidth / 2d;
// y轴坐标 = view半径-cos*lengthC+文字一半高度
results[1] = viewRadius - Math.cos(Math.PI / 180 * degrees) * lengthC + timeTextHeight / 2d;
return results;
}
/**
* 画时针
* 时针长度设置为时钟半径的2/3
* 内时针长度设置为时针长度的1/4
*/
private void drawHourHand(Canvas canvas) {
canvas.save();
Calendar calendar = Calendar.getInstance();
int hours = calendar.get(Calendar.HOUR);
int minutes = calendar.get(Calendar.MINUTE);
// 这里计算时钟总和,在不同的区间内时针显示的阴影方向不同
float totalHours = minutes == 0 ? hours : hours + minutes / 60f;
if (!isDarkMode) {
if (totalHours % 6 == 0) {
hourHandPaint.setShadowLayer(8, 0, 0, Color.GRAY);
} else if (totalHours < 6) {
hourHandPaint.setShadowLayer(8, 5, -5, Color.GRAY);
} else {
hourHandPaint.setShadowLayer(8, -5, -5, Color.GRAY);
}
}
int hourHandLength = clockRadius * 2 / 3;
int hourHandLength2 = hourHandLength / 4;
canvas.rotate((hours + minutes / 60f) * 30, viewRadius, viewRadius);
canvas.drawLine(viewRadius, viewRadius - hourHandLength, viewRadius, viewRadius, hourHandPaint);
canvas.drawLine(viewRadius, viewRadius - hourHandLength + inHandMargin, viewRadius, viewRadius - hourHandLength + inHandMargin + hourHandLength2, hourHandPaint2);
canvas.restore();
}
/**
* 画分针
* 分针长度设置为时钟半径的4/5
* 内分针长度设置为分针长度的1/5
*/
private void drawMinuteHand(Canvas canvas) {
canvas.save();
Calendar calendar = Calendar.getInstance();
int minutes = calendar.get(Calendar.MINUTE);
int seconds = calendar.get(Calendar.SECOND);
// 这里计算分钟总和,在不同的区间内分针显示的阴影方向不同
float totalMinutes = seconds == 0 ? minutes : minutes + seconds / 60f;
if (!isDarkMode) {
if (totalMinutes % 30 == 0) {
minuteHandPaint.setShadowLayer(8, 0, 0, Color.GRAY);
} else if (totalMinutes < 30) {
minuteHandPaint.setShadowLayer(8, 5, -5, Color.GRAY);
} else {
minuteHandPaint.setShadowLayer(8, -5, -5, Color.GRAY);
}
}
int minuteHandLength = clockRadius * 4 / 5;
int minuteHandLength2 = minuteHandLength / 4;
canvas.rotate((minutes + seconds / 60f) * 6, viewRadius, viewRadius);
canvas.drawLine(viewRadius, viewRadius - minuteHandLength, viewRadius, viewRadius, minuteHandPaint);
canvas.drawLine(viewRadius, viewRadius - minuteHandLength + inHandMargin, viewRadius, viewRadius - minuteHandLength + inHandMargin + minuteHandLength2, minuteHandPaint2);
canvas.restore();
}
/**
* 画秒针
*/
private void drawSecondHand(Canvas canvas) {
canvas.save();
Calendar calendar = Calendar.getInstance();
int seconds = calendar.get(Calendar.SECOND);
// 在不同的区间内秒针显示的阴影方向不同
if (!isDarkMode) {
if (seconds % 30 == 0) {
secondHandPaint.setShadowLayer(8, 0, 0, Color.GRAY);
} else if (seconds < 30) {
secondHandPaint.setShadowLayer(8, 5, -5, Color.GRAY);
} else {
secondHandPaint.setShadowLayer(8, -5, -5, Color.GRAY);
}
}
canvas.rotate(seconds * 6, viewRadius, viewRadius);
canvas.drawLine(viewRadius, outMargin, viewRadius, viewRadius + SizeUtils.dp2px(getContext(), 10), secondHandPaint);
secondHandPaint2.setColor(Color.RED);
canvas.drawCircle(viewRadius, viewRadius, SizeUtils.dp2px(getContext(), 5), secondHandPaint2);
secondHandPaint2.setColor(Color.WHITE);
canvas.drawCircle(viewRadius, viewRadius, SizeUtils.dp2px(getContext(), 2), secondHandPaint2);
canvas.restore();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
reMeasure(widthMeasureSpec, heightMeasureSpec);
}
/**
* 重新测量
*/
private void reMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 测量宽度
int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
// 测量高度
int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
// 最大或者未指定大小,这种就设置默认大小
setMeasuredDimension(defaultWidth, defaultHeight);
} else if (widthMode == MeasureSpec.EXACTLY || heightMode == MeasureSpec.EXACTLY) {
// 确定大小,取宽高中的最小值
int min = Math.min(measureWidth, measureHeight);
setMeasuredDimension(min, min);
} else {
setMeasuredDimension(defaultWidth, defaultHeight);
}
viewRadius = getMeasuredWidth() / 2;
// 时钟半径 = View宽高/2 - 外边距
clockRadius = getMeasuredWidth() / 2 - outMargin;
}
}