Android 用Canvas轻松绘制一个时钟

接下来这篇文章主要是结束如何轻松自定义一个View并使用Canvas绘制一个时钟的案例,话不多说先上图瞅瞅,一共实现了两种效果,一种是秒动(秒针一秒走一针)、另一种是连动式的,秒针不会停会一直走动,话不多说,先看看下面两张效果图。


秒动.gif

连动式.gif

好了上面的图就是目前两种效果,纯原生代码实现,思路也比较简单,根据Canvas绘制一个紫色圆形背景、然后绘制刻度尺、接着绘制时针、分针、秒针,其中主要的是时分秒三个指针是如何转动的,下面来说说具体思路。

1、绘制表盘

首先我们先来简单看一下如何绘制表盘,因为这里比较简单,我们直接上代码看一下就能明白。

//绘制紫色背景
private void drawClockBg(Canvas canvas) {
        mClockBgPaint.setStyle(Paint.Style.FILL);
        mClockBgPaint.setColor(0xFF6200EE);
        canvas.drawCircle(
                mWidth / 2f,
                mHeight / 2f,
                centerPoint - marginOffset,
                mClockBgPaint
        );
    }
//绘制刻度尺
 private void drawScaleLine(Canvas canvas) {
        canvas.save();
        //循环绘制刻度线
        for (int i = 0; i < 60; i++) {
            canvas.drawLine(
                    centerPoint,
                    marginOffset,
                    centerPoint,
                    (i % 5 == 0) ? mScaleLongSize : mScaleSize,
                    mClockScalePaint
            );
            canvas.rotate(CLockUtils.CLOCK_CYCLE / CLockUtils.CLOCK_SCALE_SIZE,
                    Math.min(mWidth, mHeight) / 2f,
                    Math.min(mWidth, mHeight) / 2f);
        }
        canvas.restore();
    }

这里非常的简单刻度主要循环旋转画布绘制每秒钟/分钟的刻度,其中小时刻度会比分钟/秒钟刻度稍长一些,然后就是绘制重要知识点,就是此处使用了canvas.save & canvas.restore()其主要作用是为了不影响其它画布的绘制。

canvas.save 保存当前矩阵和剪辑到一个私有堆栈。后续调用转换、缩放、旋转、倾斜、concat或clipRect,clipPath将所有操作照常。这里直白点理解可以理解为把先前画布的效果存起来然后再作画,绘制不会影响先前的画布状态。
canvas.restore() 恢复画布先前保存的状态。

2绘制时针、分针、秒针

刻度绘制完后我们接下来需要开始绘制时针、分针、秒针了。这三根指针的绘制操作其实是一致的包括旋转,这里为了减少代码冗余,抽出公共方法来处理,只不过他们绘制的画笔参数以及旋转角度等参数不同,我们来看下代码。

//绘制时针、分钟、秒针的公共方法
 private void drawPointer(Canvas canvas, float degress, float startX, float startY,
                             float stopX, float stopY, @androidx.annotation.NonNull Paint paint) {
        //保存画布当前状态
        canvas.save();
        //旋转秒针画布
        canvas.rotate(degress, centerPoint, centerPoint);
        //绘制秒针
        canvas.drawLine(startX, startY, stopX, stopY, paint);
        //恢复画布状态
        canvas.restore();
    }

这里也非常好理解,代码也做了精剪,这里并非主要的逻辑,这里仅仅是根据传入的角度degress来控制旋转角度,主要逻辑是如何使指针转动起来并且是准确的转动,这里是通过自己计算后得来的,具体对代码做了详细的注释,来上代码:

//根据时间计算时针、分针、秒针的旋转角度
    private void calculateDegrees() {
        minuteDegrees = 0;
        hourDegrees = 0;

        //获取当前小时数
        //当前小时数
        int currentHour = Calendar.getInstance().get(Calendar.HOUR);
        //获取当前分钟数
        //当前分钟数
        int currentMinute = Calendar.getInstance().get(Calendar.MINUTE);
        //计算分针旋转的角度(当前分钟数 / 刻度数60° * 周期数360°)
        minuteDegrees = currentMinute / CLockUtils.CLOCK_SCALE_SIZE * CLockUtils.CLOCK_CYCLE;
        //根据分针旋转角度,计算时针偏移量(分钟旋转角度 / 周期数360° * 一小时间隔角度30°) + (一小时间隔角度30° * 当前小时数)
        float minutesDegressOffset = (minuteDegrees / CLockUtils.CLOCK_CYCLE * 30) + (30 * currentHour);
        //计算时针旋转的角度(当前小时数 / 一周小时总数12小时 / 一周刻度数60 * 周期数360° + 分钟偏移量)
        hourDegrees = currentHour / CLockUtils.CLOCK_HOURS_SIZE /
                CLockUtils.CLOCK_SCALE_SIZE * CLockUtils.CLOCK_CYCLE + minutesDegressOffset;

        //获取当前秒数
        currentSecond = Calendar.getInstance().get(Calendar.SECOND);
        //计算秒针旋转的角度(当前秒数/刻度数60°/周期数360°)
        secondDegrees = currentSecond / CLockUtils.CLOCK_SCALE_SIZE * CLockUtils.CLOCK_CYCLE;
    }

这里的计算逻辑稍微复杂一些简单来说一下逻辑这里以秒动模式为例,首先秒针转动每秒转动一个刻度,总共60个刻度,那么获取当前秒数 / 刻度数60 / 一周是360度得出每秒的角度,分钟也是同样的逻辑,根据分钟数来计算,这里比较复杂的是时针的角度了,首先计算分钟的角度(当前分钟数 / 刻度数60° * 周期数360°)。

然后由于分针转动一周,时针转动的角度是一个大刻度也就是比如现在是3点,时针指向3的刻度,如果分针转动一周就是4点钟,那么3-4就是一个大刻度就是30度角,那么此时根据分针旋转角度,计算时针偏移量(分钟旋转角度 / 周期数360° * 一小时间隔角度30°) + (一小时间隔角度30° * 当前小时数),这个偏移量就好比假设分针从0刻度就是12点这个刻度位置走到了3点半的刻度,其实时针是会有转动的,不会继续停留在3的刻度,而是3-4之间,这个偏移量就是计算时针走了多少的,最后根据我自己思考的出的公式,计算时针旋转的角度(当前小时数 / 一周小时总数12小时 / 一周刻度数60 * 周期数360° + 分钟偏移量)。这里也是花了比较多的时间来思考的,也是主要的算法逻辑,看不懂的小伙伴可以多看几遍或者自己上手敲一敲,或者跟一下我的代码。

3执行转动(秒动还是连动)

这里处理指针转动主要是基于Android的消息机制,使用Handler间隔1000ms来发送消息(这是秒动逻辑),连动则是根据当前秒数与60s的时间差来,然后通过属性动画ValueAnimator来做秒针动画,下面来看下代码你就明白了。

//旋转指针
    private void rotatePointer() {
        calculateDegrees();
        //秒动
        if (isSecondAnimator) {
            invalidate();
            mHandler.postDelayed(mClockRunnable, DELAYM_ILLIS);
            return;
        }
        //非秒动
        long delayMillts = DELAYM_ILLIS * (60 - currentSecond);
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(secondDegrees, CLockUtils.CLOCK_CYCLE);
        valueAnimator.setDuration(delayMillts);
        valueAnimator.setInterpolator(null);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                secondDegrees = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        valueAnimator.start();
        mHandler.postDelayed(mClockRunnable, delayMillts);
    }

秒动大家都能理解就是一秒钟发一次消息,请求绘制一下即可,连动比较复杂,是根据获取的当前秒数与60做差值然后乘1000ms得来的,例如现在打开时钟控件页面,此时的秒数是45s,那么秒针初始化位置是45s的位置,那么走完60s就只剩15s的时间了,所以这里的delayMillts就是当前秒数与60s的差值,然后做动画即可。

家人们逻辑说到这里基本上就说完了,这里代码都是一片段的形式附上的,具体代码我已经上传到github上了,有兴趣的小伙伴可以checkout了解一下。
GitHub链接>>>点击跳转
家人们觉得不错的话,github给个star哈,感恩!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容