一、功能介绍
自定义动态时钟组件,一个动态时钟表盘,包括时针、分针和秒针的实时更新
二、属性说明
-
mRadius
:时钟的半径,控制整体大小 -
mCircleColor
:时钟外圆的颜色 -
mCircleWidth
:时钟外圆边框的宽度 -
mTextSize
:时钟刻度文字的字体大小 -
mTextColor
:时钟刻度文字的颜色 -
mBigScaleColor
:大刻度(主要刻度线)的颜色 -
mMiddlecaleColor
:中刻度(次要刻度线)的颜色 -
mSmallScaleColor
:小刻度(微小刻度线)的颜色 -
mHourHandColor
:时针的颜色 -
mMinuteHandColor
:分针的颜色 -
mSecondHandColor
:秒针的颜色 -
mHourHandWidth
:时针的宽度 -
mMinuteHandWidth
:分针的宽度 -
mSecondHandWidth
:秒针的宽度
三、效果图
四、代码实现
ClockView
的完整代码实现:
/**
* @Description: 自定义时钟
* @Author: 会叫的青蛙
* @Date: 2024/1/15 13:58
*/
public class ClockView extends View {
//文字画笔对象
private Paint mTextPaint;
//圆,指针,刻度画笔
private Paint mPaint;
//半径
public float mRadius;
//外圆的颜色
public int mCircleColor;
// 外圆的宽度
public float mCircleWidth;
//文字的大小
public float mTextSize;
//文字的颜色
public int mTextColor;
//大刻度颜色
public int mBigScaleColor;
//中刻度
public int mMiddlecaleColor;
//小刻度颜色
public int mSmallScaleColor;
//时针颜色
public int mHourHandColor;
//分针颜色
public int mMinuteHandColor;
//秒针颜色
public int mSecondHandColor;
//时针宽度
public float mHourHandWidth;
//分针宽度
public float mMinuteHandWidth;
//秒针宽度
public float mSecondHandWidth;
//控件宽度
public int mWidth;
//控件高度
public int mHeght;
//handlerThread
private HandlerThread handlerThread;
private Handler backgroundHandler;
public ClockView(Context context) {
this(context, null);
}
public ClockView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ClockView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ClockView);
mRadius = typedArray.getDimension(R.styleable.ClockView_mRadius, 400);
mCircleColor = typedArray.getColor(R.styleable.ClockView_mCircleColor, Color.WHITE);
mCircleWidth = typedArray.getDimension(R.styleable.ClockView_mCircleWidth, 20);
mTextSize = typedArray.getDimension(R.styleable.ClockView_mCircleWidth, 40);
mTextColor = typedArray.getColor(R.styleable.ClockView_mTextColor, Color.DKGRAY);
mBigScaleColor = typedArray.getColor(R.styleable.ClockView_mBigScaleColor, Color.BLACK);
mSmallScaleColor = typedArray.getColor(R.styleable.ClockView_mSmallScaleColor, Color.RED);
mMiddlecaleColor = typedArray.getColor(R.styleable.ClockView_mMiddlecaleColor, Color.BLACK);
mHourHandColor = typedArray.getColor(R.styleable.ClockView_mHourHandColor, Color.BLACK);
mMinuteHandColor = typedArray.getColor(R.styleable.ClockView_mMinuteHandColor, Color.BLACK);
mSecondHandColor = typedArray.getColor(R.styleable.ClockView_mSecondHandColor, Color.BLACK);
mHourHandWidth = typedArray.getDimension(R.styleable.ClockView_mHourHandWidth, 20);
mMinuteHandWidth = typedArray.getDimension(R.styleable.ClockView_mMinuteHandWidth, 10);
mSecondHandWidth = typedArray.getDimension(R.styleable.ClockView_mSecondHandWidth, 5);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
mTextPaint = new Paint();
mTextPaint.setAntiAlias(true);
mTextPaint.setStyle(Paint.Style.FILL);
mTextPaint.setTextSize(mTextSize);
mTextPaint.setColor(mTextColor);
// 初始化 HandlerThread
handlerThread = new HandlerThread("ClockViewHandlerThread");
handlerThread.start();
backgroundHandler = new Handler(handlerThread.getLooper());
// 开始时钟更新
backgroundHandler.post(clockRunnable);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureSize(widthMeasureSpec), measureSize(heightMeasureSpec));
}
private int measureSize(int mMeasureSpec) {
int result;
int mode = MeasureSpec.getMode(mMeasureSpec);
int size = MeasureSpec.getSize(mMeasureSpec);
if (mode == MeasureSpec.EXACTLY) {
result = size;
} else {
result = 400;
if (mode == MeasureSpec.AT_MOST) {
result = Math.min(result, size);
}
}
return result;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//设置宽高、半径
mWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
mHeght = getMeasuredHeight() - getPaddingBottom() - getPaddingTop();
mRadius = Math.min(mWidth / 2, mHeght / 2);
//首先绘制圆
drawCircle(canvas);
//绘制刻度
drawScale(canvas);
//绘制指针
drawPointer(canvas);
}
/**
* 画圆
* @param canvas
*/
private void drawCircle(Canvas canvas) {
mPaint.setStrokeWidth(mCircleWidth);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mCircleColor);
canvas.drawCircle(mWidth / 2, mHeght / 2, mRadius, mPaint);
// 绘制第一个白色圆圈
float density = getResources().getDisplayMetrics().density;
float strokeWidth = 2 * density; // dp to pixels
mPaint.setStrokeWidth(strokeWidth);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.WHITE);
canvas.drawCircle(mWidth / 2, mHeght / 2, mRadius - strokeWidth / 2, mPaint);
// 绘制第二个白色圆圈
float gap = 3 * density; // dp to pixels
canvas.drawCircle(mWidth / 2, mHeght / 2, mRadius - mCircleWidth / 2 - strokeWidth / 2 - gap, mPaint);
}
/**
* 刻度和文字(文字方向为时钟的刻度)
* @param canvas
*/
/*private void drawScale(Canvas canvas) {
mWidth = mWidth - 50; // 将宽度减少50个像素
mHeght = mHeght - 50; // 将高度减少50个像素
canvas.translate(25f, 25f); // 将画布平移25个像素
for (int i = 0; i < 60; i++) {
//设置大刻度
if (i == 0 || i == 15 || i == 30 || i == 45) {
mPaint.setStrokeWidth(8);
mPaint.setColor(mBigScaleColor);
canvas.drawLine(mWidth / 2, mHeght / 2 - mWidth / 2 + mCircleWidth / 2,
mWidth / 2, mHeght / 2 - mWidth / 2 + mCircleWidth / 2 + 25, mPaint);
String scaleTv = String.valueOf(i == 0 ? 12 : i / 5);
canvas.drawText(scaleTv, mWidth / 2 - mTextPaint.measureText(scaleTv) / 2,
mHeght / 2 - mWidth / 2 + mCircleWidth / 2 + 75, mTextPaint);
} else if (i == 5 || i == 10 || i == 20 || i == 25 || i == 35 || i == 40 || i == 50 || i == 55)
//设置中刻度
{
mPaint.setStrokeWidth(8);
mPaint.setColor(mMiddlecaleColor);
canvas.drawLine(mWidth / 2, mHeght / 2 - mWidth / 2 + mCircleWidth / 2,
mWidth / 2, mHeght / 2 - mWidth / 2 + mCircleWidth / 2 + 25, mPaint);
String scaleTv = String.valueOf(i / 5);
canvas.drawText(scaleTv, mWidth / 2 - mTextPaint.measureText(scaleTv) / 2,
mHeght / 2 - mWidth / 2 + mCircleWidth / 2 + 75, mTextPaint);
} else
//设置小刻度
{
mPaint.setColor(mSmallScaleColor);
mPaint.setStrokeWidth(4);
canvas.drawLine(mWidth / 2, mHeght / 2 - mWidth / 2 + mCircleWidth / 2,
mWidth / 2, mHeght / 2 - mWidth / 2 + mCircleWidth + 10, mPaint);
}
canvas.rotate(6, mWidth / 2, mHeght / 2);
}
}*/
/**
* 刻度和文字(优化:分开绘制,文字方向垂直)
* @param canvas
*/
private void drawScale(Canvas canvas) {
mWidth = mWidth - 50; // 将宽度减少50个像素
mHeght = mHeght - 50; // 将高度减少50个像素
canvas.translate(25f, 25f); // 将画布平移25个像素
for (int i = 0; i < 60; i++) {
//设置大刻度
if (i == 0 || i == 15 || i == 30 || i == 45) {
mPaint.setStrokeWidth(8);
mPaint.setColor(mBigScaleColor);
canvas.drawLine(mWidth / 2, mHeght / 2 - mWidth / 2 + mCircleWidth / 2,
mWidth / 2, mHeght / 2 - mWidth / 2 + mCircleWidth / 2 + 25, mPaint);
} else if (i == 5 || i == 10 || i == 20 || i == 25 || i == 35 || i == 40 || i == 50 || i == 55) {
//设置中刻度
mPaint.setStrokeWidth(8);
mPaint.setColor(mMiddlecaleColor);
canvas.drawLine(mWidth / 2, mHeght / 2 - mWidth / 2 + mCircleWidth / 2,
mWidth / 2, mHeght / 2 - mWidth / 2 + mCircleWidth / 2 + 25, mPaint);
} else {
//设置小刻度
mPaint.setColor(mSmallScaleColor);
mPaint.setStrokeWidth(4);
canvas.drawLine(mWidth / 2, mHeght / 2 - mWidth / 2 + mCircleWidth / 2,
mWidth / 2, mHeght / 2 - mWidth / 2 + mCircleWidth + 10, mPaint);
}
canvas.rotate(6, mWidth / 2, mHeght / 2);
}
// 绘制文字
for (int i = 0; i < 60; i += 5) {
String scaleTv = String.valueOf(i == 0 ? 12 : i / 5);
canvas.save();
canvas.rotate(6 * i, mWidth / 2, mHeght / 2);
canvas.rotate(-6 * i, mWidth / 2, mHeght / 2 - mWidth / 2 + mCircleWidth / 2 + 65);
canvas.drawText(scaleTv, mWidth / 2 - mTextPaint.measureText(scaleTv) / 2,
mHeght / 2 - mWidth / 2 + mCircleWidth / 2 + 80, mTextPaint);
canvas.restore();
}
}
/**
* 绘制指针
* @param canvas
*/
private void drawPointer(Canvas canvas) {
Calendar mCalendar = Calendar.getInstance();
//获取当前小时数
int hours = mCalendar.get(Calendar.HOUR);
//获取当前分钟数
int minutes = mCalendar.get(Calendar.MINUTE);
//获取当前秒数
int seconds = mCalendar.get(Calendar.SECOND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
//绘制时针
canvas.save();
mPaint.setColor(mHourHandColor);
mPaint.setStrokeWidth(mHourHandWidth);
//这里计算时针需要旋转的角度 实现原理是计算出一共多少分钟除以60计算出真实的小时数(带有小数,为了更加准确计算度数),已知12小时是360度,现在求出了实际小时数比例求出角度
Float hoursAngle = (hours * 60 + minutes) / 60f / 12f * 360;
canvas.rotate(hoursAngle, mWidth / 2, mHeght / 2);
canvas.drawLine(mWidth / 2, mHeght / 2 - mWidth / 2f * 0.5f, mWidth / 2, mHeght / 2 + mWidth / 2f * 0.15f, mPaint);
canvas.restore();
//绘制分针
canvas.save();
mPaint.setColor(mMinuteHandColor);
mPaint.setStrokeWidth(mMinuteHandWidth);
//这里计算分针需要旋转的角度 60分钟360度,求出实际分钟数所占的度数
Float minutesAngle = (minutes * 60 + seconds) / 60f / 60f * 360;
canvas.rotate(minutesAngle, mWidth / 2, mHeght / 2);
canvas.drawLine(mWidth / 2, mHeght / 2 - mWidth / 2f * 0.7f, mWidth / 2, mHeght / 2 + mWidth / 2f * 0.15f, mPaint);
canvas.restore();
//绘制中间的圆圈
canvas.save();
mPaint.setColor(mSecondHandColor);
mPaint.setStrokeWidth(mSecondHandWidth);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawCircle(mWidth / 2, mHeght / 2, 12, mPaint);
canvas.restore();
//绘制秒针
canvas.save();
mPaint.setColor(mSecondHandColor);
mPaint.setStrokeWidth(mSecondHandWidth);
mPaint.setStyle(Paint.Style.STROKE);
//这里计算秒针需要旋转的角度 60秒360度,求出实际秒数所占的度数
Float secondAngle = seconds / 60f * 360;
canvas.rotate(secondAngle, mWidth / 2, mHeght / 2);
canvas.drawLine(mWidth / 2, mHeght / 2 - mWidth / 2f * 0.8f, mWidth / 2, mHeght / 2 + mWidth / 2f * 0.2f, mPaint);
canvas.restore();
// 绘制小圆圈
canvas.save();
mPaint.setColor(mCircleColor);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawCircle(mWidth / 2, mHeght / 2, 6, mPaint);
canvas.restore();
}
/**
* 时钟更新
*/
private Runnable clockRunnable = new Runnable() {
@Override
public void run() {
// 更新时钟
postInvalidate();
// 安排下一次更新
backgroundHandler.postDelayed(this, 1000);
}
};
// 重写 onDetachedFromWindow 方法以清理 HandlerThread
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
handlerThread.quitSafely();
}
}
自定义属性:
<!--时钟属性-->
<declare-styleable name="ClockView">
<attr name="mRadius" format="dimension"/>
<attr name="mCircleColor" format="color"/>
<attr name="mCircleWidth" format="dimension"/>
<attr name="mTextSize" format="dimension"/>
<attr name="mTextColor" format="color"/>
<attr name="mBigScaleColor" format="color"/>
<attr name="mMiddlecaleColor" format="color"/>
<attr name="mSmallScaleColor" format="color"/>
<attr name="mHourHandColor" format="color"/>
<attr name="mMinuteHandColor" format="color"/>
<attr name="mSecondHandColor" format="color"/>
<attr name="mHourHandWidth" format="dimension"/>
<attr name="mMinuteHandWidth" format="dimension"/>
<attr name="mSecondHandWidth" format="dimension"/>
</declare-styleable>
五、使用方法
<!--时钟组件-->
<com.view.ClockView
android:layout_width="249dp"
android:layout_height="249dp"
android:layout_marginTop="20dp"
android:layout_gravity="center"
app:mBigScaleColor="#5b616f"
app:mCircleColor="#f6f8fd"
app:mHourHandColor="#BAC1D2"
app:mHourHandWidth="5dp"
app:mMiddlecaleColor="#5b5e67"
app:mMinuteHandColor="#BAC1D2"
app:mMinuteHandWidth="2dp"
app:mSecondHandColor="#EB420B"
app:mSecondHandWidth="1dp"
app:mSmallScaleColor="#414651"
app:mTextColor="#333333"
app:mTextSize="16sp" />