Android项目杰尼杰尼(极简番茄工作法)的学习准备之Android时钟

    xiaoxiao2023-11-26  192

    Android项目杰尼杰尼(极简番茄工作法)的学习准备之Android时钟

    Android时钟需求分析绘制过程其他步骤实现过程1.绘制时钟的圆形和刻度总结: 2.绘制特殊时刻(12点、3点、6点、9点)的数字总结: 绘制指针指针时针总结: 分针与秒针总结: 时针的自适应总结: 自定义时钟属性总结: 开启计时总结: 整体总结

    这是一篇学习笔记,因为期末项目的杰尼杰尼(极简番茄工作法),为了实现其中的番茄钟功能,学习了如何实现Android时钟。

    Android时钟

    先来看看实现效果的动图。

    需求分析

    首先,来看一张我家里的时钟的照片,以此确定好需求分析,我们需要: 1.一个圆形的表盘; 2.有大的12个刻度,每个大刻度下有五个小刻度; 3.有三根长短不一的针,最短的

    绘制过程

    1.绘制出大圆盘作为时钟的表盘 2.绘制出四个重要刻度点的数字 3.绘制三根指针

    其他步骤

    1.做自适应 2.自定义时钟属性 3.开启计时

    实现过程

    那么开始具体的实现过程,使用**ondraw()**方法来实现绘制过程。

    @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // 坐标原点移动到View 中心 canvas.translate(mCenterX, mCenterY); // 绘制时钟的圆形和刻度 drawCircle(canvas); // 绘制特殊时刻(12点、3点、6点、9点)的数字 drawText(canvas); // 绘制指针 drawPointer(canvas); }

    1.绘制时钟的圆形和刻度

    /** * 绘制时钟的圆形和刻度 */ private void drawCircle(Canvas canvas) { mDefaultPaint.setStrokeWidth(mDefaultScaleWidth); mDefaultPaint.setColor(mClockColor); // 画圆 canvas.drawCircle(0, 0, mRadius, mDefaultPaint); for (int i = 0; i < 60; i++) { if (i % 5 == 0) { // 特殊时刻 mDefaultPaint.setStrokeWidth(mParticularlyScaleWidth); mDefaultPaint.setColor(mColorParticularyScale); canvas.drawLine(0, -mRadius, 0, -mRadius + mParticularlyScaleLength, mDefaultPaint); } else { // 一般时刻 mDefaultPaint.setStrokeWidth(mDefaultScaleWidth); mDefaultPaint.setColor(mColorDefaultScale); canvas.drawLine(0, -mRadius, 0, -mRadius + mDefaultScaleLength, mDefaultPaint); } canvas.rotate(6); } }

    总结:

    1.画刻度线时,分为两种,一种的普通刻度线,也就是每分钟的刻度线;另一种就是每小时的刻度线,也就是每隔5歌绘制一个特俗刻度线。 2.采用canvas.drawCircle(0, 0, mRadius, mDefaultPaint);来画圆。 3.因为一圈是360度,一共有60分钟,所以每个刻度线就是6度;则采用canvas.rotate(6)旋转6度来绘制刻度线。

    2.绘制特殊时刻(12点、3点、6点、9点)的数字

    /** * 绘制特殊时刻(12点、3点、6点、9点)的文字 */ private void drawText(Canvas canvas) { setTextPaint(); Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics(); // 文字顶部与基线距离 float ascent = Math.abs(fontMetrics.ascent); // 文字底部与基线距离 float descent = Math.abs(fontMetrics.descent); // 文字高度 float fontHeight = ascent + descent; // 文字竖直中心点距离基线的距离; float offsetY = fontHeight / 2 - Math.abs(fontMetrics.descent); // 文字宽度 float fontWidth; // drawText(@NonNull String text, float x, float y, @NonNull Paint paint) 参数:y,为基线的y坐标,并非文字左下角的坐标 // 文字距离圆圈的距离为 特殊刻度长度+宽度 String h = "12"; // y轴坐标为: -(半径-特殊刻度长度-特殊刻度宽度(作为间距)-文字顶部距离基线的距离) float y = -(mRadius - mParticularlyScaleLength - mParticularlyScaleWidth - ascent); canvas.drawText(h, 0, y, mTextPaint); h = "3"; fontWidth = mTextPaint.measureText(h); // y轴坐标为: 半径-特殊刻度长度-特殊刻度宽度(作为间距)-文字长度/2(绘制原点在文字横向中心) y = mRadius - mParticularlyScaleLength - mParticularlyScaleWidth - (fontWidth / 2); canvas.drawText(h, y, 0 + offsetY, mTextPaint); h = "6"; // y轴坐标为: 半径-特殊刻度长度-特殊刻度宽度(作为间距)-文字底部与基线的距离 y = mRadius - mParticularlyScaleLength - mParticularlyScaleWidth - descent; canvas.drawText(h, 0, y, mTextPaint); h = "9"; fontWidth = mTextPaint.measureText(h); // y轴坐标为: -(半径-特殊刻度长度-特殊刻度宽度(作为间距)-文字长度/2(绘制原点在文字横向中心)) y = -(mRadius - mParticularlyScaleLength - mParticularlyScaleWidth - (fontWidth / 2)); canvas.drawText(h, y, 0 + offsetY, mTextPaint); } private void setTextPaint() { mTextPaint.setStrokeWidth(mDefaultScaleWidth / 2); mTextPaint.setTextSize(mParticularlyScaleWidth * 4); mTextPaint.setTextAlign(Paint.Align.CENTER); }

    总结:

    1.因为屏幕显示的大小问题,为了美观所以就只选取了12点,3点,6点,9点这四个特殊刻度来绘制数字。 2.使用anvas. drawText(String text, float x, float y,Paint paint)来绘制数字

    绘制指针

    绘制指针有三个部分,时针,分针,秒针; 在 drawPointer(Canvas canvas)里调用drawHourPointer(canvas); drawMinutePointer(canvas); drawSecondPointer(canvas);来分别绘制时针,分针,秒针。

    指针

    /** * 绘制指针 */ private void drawPointer(Canvas canvas) { drawHourPointer(canvas); drawMinutePointer(canvas); drawSecondPointer(canvas); mPointerPaint.setColor(mClockColor); // 绘制中心原点,需要在指针绘制完成后才能绘制 canvas.drawCircle(0, 0, mPointRadius, mPointerPaint); }

    时针

    /** * 绘制时针 */ private void drawHourPointer(Canvas canvas) { mPointerPaint.setStrokeWidth(mHourPointerWidth); mPointerPaint.setColor(mColorHourPointer); // 当前时间的总秒数 float s = mH * 60 * 60 + mM * 60 + mS; // 百分比 float percentage = s / (12 * 60 * 60); // 通过角度计算弧度值,因为时钟的角度起线是y轴负方向,而View角度的起线是x轴正方向,所以要加270度 float angle = 270 + 360 * percentage; float x = (float) (mHourPointerLength * Math.cos(Math.PI * 2 / 360 * angle)); float y = (float) (mHourPointerLength * Math.sin(Math.PI * 2 / 360 * angle)); canvas.drawLine(0, 0, x, y, mPointerPaint); }
    总结:

    1.绘制时针时,应该绘制出时针的具体位置,而不是例如5点30分时,时针指在5的刻度线上,而是要让时针跟着秒针的移动而移动。 2.所以,通过当前时间来计算出指针与x轴的角度,从而计算出具体的坐标值。 3.使用canvas.drawLine(0, 0, x, y, mPointerPaint)来绘制指针。

    分针与秒针

    /** * 绘制分针 */ private void drawMinutePointer(Canvas canvas) { mPointerPaint.setStrokeWidth(mMinutePointerWidth); mPointerPaint.setColor(mColorMinutePointer); float s = mM * 60 + mS; float percentage = s / (60 * 60); float angle = 270 + 360 * percentage; float x = (float) (mMinutePointerLength * Math.cos(Math.PI * 2 / 360 * angle)); float y = (float) (mMinutePointerLength * Math.sin(Math.PI * 2 / 360 * angle)); canvas.drawLine(0, 0, x, y, mPointerPaint); } /** * 绘制秒针 */ private void drawSecondPointer(Canvas canvas) { mPointerPaint.setStrokeWidth(mSecondPointerWidth); mPointerPaint.setColor(mColorSecondPointer); float s = mS; float percentage = s / 60; float angle = 270 + 360 * percentage; float x = (float) (mSecondPointerLength * Math.cos(Math.PI * 2 / 360 * angle)); float y = (float) (mSecondPointerLength * Math.sin(Math.PI * 2 / 360 * angle)); canvas.drawLine(0, 0, x, y, mPointerPaint); }
    总结:

    同理与时针的绘制,就不阐述了。

    时针的自适应

    @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = getMeasureSize(true, widthMeasureSpec); int height = getMeasureSize(false, heightMeasureSpec); setMeasuredDimension(width, height); } /** * 获取View尺寸 * 基本上算是标准写法 * * @param isWidth 是否是width,不是的话,是height */ private int getMeasureSize(boolean isWidth, int measureSpec) { int result = 0; int specSize = MeasureSpec.getSize(measureSpec); int specMode = MeasureSpec.getMode(measureSpec); switch (specMode) { case MeasureSpec.UNSPECIFIED: if (isWidth) { result = getSuggestedMinimumWidth(); } else { result = getSuggestedMinimumHeight(); } break; case MeasureSpec.AT_MOST: if (isWidth) result = Math.min(specSize, mWidth); else result = Math.min(specSize, mHeight); break; case MeasureSpec.EXACTLY: result = specSize; break; } return result; } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mWidth = w; mHeight = h; mCenterX = w / 2; mCenterY = h / 2; mRadius = (float) (w / 2 * 0.8); initClockPointerLength(); } /** * 根据控件的大小,初始化时钟刻度的长度和宽度、指针的长度和宽度、时钟中心点的半径 */ private void initClockPointerLength() { /* * 默认时钟刻度长=半径/10; * 默认时钟刻度宽=长/6; * * */ mDefaultScaleLength = mRadius / 10; mDefaultScaleWidth = mDefaultScaleLength / 6; /* * 特殊时钟刻度长=半径/5; * 特殊时钟刻度宽=长/6; * * */ mParticularlyScaleLength = mRadius / 5; mParticularlyScaleWidth = mParticularlyScaleLength / 6; /* * 时针长=半径/3; * 时针宽=特殊时钟刻度宽; * * */ mHourPointerLength = mRadius / 3; mHourPointerWidth = mParticularlyScaleWidth; /* * 分针长=半径/2; * 分针宽=特殊时钟刻度宽; * * */ mMinutePointerLength = mRadius / 2; mMinutePointerWidth = mParticularlyScaleWidth; /* * 秒针长=半径/3*2; * 秒针宽=默认时钟刻度宽; * * */ mSecondPointerLength = mRadius / 3 * 2; mSecondPointerWidth = mDefaultScaleWidth; // 中心点半径=(默认刻度宽+特殊刻度宽)/2 mPointRadius = (mDefaultScaleWidth + mParticularlyScaleWidth) / 2; }

    总结:

    1.使用onMeasure(int widthMeasureSpec, int heightMeasureSpec),来测量出尺寸。 2.时针的尺寸,默认时钟刻度长=半径/10,默认时钟刻度宽=长/6; 3.特殊时刻,特殊时钟刻度长=半径/5,特殊时钟刻度宽=长/6; 4.时针,时针长=半径/3,时针宽=特殊时钟刻度宽; 5.分针,分针长=半径/2,分针宽=特殊时钟刻度宽; 6.秒针,秒针长=半径/3*2,秒针宽=默认时钟刻度宽; 7.中心点半径,中心点半径=(默认刻度宽+特殊刻度宽)/2;

    自定义时钟属性

    通过attr.xml来自定义时钟属性

    <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="ClockView"> <attr name="clockColor" format="color"/> <attr name="defaultScaleColor" format="color"/> <attr name="particularlyScaleColor" format="color"/> <attr name="hourPointerColor" format="color"/> <attr name="minutePointerColor" format="color"/> <attr name="secondPointerColor" format="color"/> </declare-styleable> </resources> private void getAttrs(Context context, AttributeSet attrs) { TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ClockView); mClockColor = a.getColor(R.styleable.ClockView_clockColor, Color.GRAY); mColorDefaultScale = a.getColor(R.styleable.ClockView_defaultScaleColor, mClockColor); mColorParticularyScale = a.getColor(R.styleable.ClockView_particularlyScaleColor, mClockColor); mColorHourPointer = a.getColor(R.styleable.ClockView_hourPointerColor, Color.BLACK); mColorMinutePointer = a.getColor(R.styleable.ClockView_minutePointerColor, Color.BLACK); mColorSecondPointer = a.getColor(R.styleable.ClockView_secondPointerColor, mClockColor); }

    通过background设置时钟的背景

    android:background="#FFFAF0"

    总结:

    1.使用mClockColor = a.getColor(R.styleable.ClockView_clockColor, Color.GRAY);来设置时钟的画笔颜色为灰色。 2. mColorHourPointer = a.getColor(R.styleable.ClockView_hourPointerColor, Color.BLACK); mColorMinutePointer = a.getColor(R.styleable.ClockView_minutePointerColor, Color.BLACK);设置时针和分针的颜色为黑色

    开启计时

    /** * 开始计时 */ private void startTime() { new Thread(new Runnable() { @Override public void run() { while (true) { getTime(); } } }).start(); } /** * 获取当前系统时间 */ private void getTime() { Calendar calendar = Calendar.getInstance(); int hour = calendar.get(Calendar.HOUR); hour = hour % 12; int minute = calendar.get(Calendar.MINUTE); int second = calendar.get(Calendar.SECOND); if (hour != mH || minute != mM || second != mS) { setTime(hour, minute, second); postInvalidate(); } } /** * 设置时间 */ private void setTime(int h, int m, int s) { mH = h; mM = m; mS = s; }

    总结:

    1.因为时钟是动态的,每秒都在变化,所以使用线程来不断的刷新当前的view。 2.通过int hour = calendar.get(Calendar.HOUR);int minute = calendar.get(Calendar.MINUTE);int second = calendar.get(Calendar.SECOND);来获取当前的系统时间,其中hour要转换成12进制的。 3.在构造方法中调用startTime()方法。

    整体总结

    通过这次Android时钟的学习,让我为期末大实验的项目杰尼杰尼(极简番茄工作法)的完成有了更大的信心。随着之后的深入学习,我觉得我的项目会圆满实现的。

    作者:庄超 原文地址

    最新回复(0)