Android自定义时钟View的实现

一、功能介绍

自定义动态时钟组件,一个动态时钟表盘,包括时针、分针和秒针的实时更新

二、属性说明

  • 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" />

参考文章:Android自定义一个属于自己的时间钟表_r.styleable.clock-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值