自定义 View 实践(二)- 简易时钟

作者: Arnold_J | 来源:发表于2017-12-11 08:51 被阅读64次

    这篇文章是基于以下两篇文章的实践:
    1.自定义View - 基础
    2.自定义View - Canvas - 图形绘制
    3.自定义 View - Canvas - 画布操作和快照


    GIF.gif

    时钟的大致效果如上,用到的主要有图形的绘制,画布操作和快照。可能还是有点丑,唉,我发誓最后会做一个 B 格高的东西。

    下面来说说这个自定义 View。

    需要实现一个视图之前,我们首先要将它拆分成各个模块,这样能方便我们构思各个模块的实现方式。

    而这个时钟我把它分为两部分:表盘和时针。其中,表盘分为三部分,圆、刻度、数字;指针有三个,时、分、秒。

    1.初始化数据

    绘制时钟我们需要的主要有这样一些数据:

    • 画笔
      • 时分秒
      • 刻度
      • 表盘
      • 数字
    • 数据
      • 表盘大小
      • 刻度长度
      • 指针长度
      • 指针多余长度
      • 当前时间

    这里我做了简单的初始化:

        private void init() {
            initBasePaint();
            initClockPaint();
            initHourPaint();
            initMinPaint();
            initSecPaint();
            initTextPaint();
    
            //表盘半径
            mRadio = 400;
            //刻度长度
            mSmallTick = 20;
            mMidTick = 40;
            mBigTick = 60;
            //指针长度
            mHourLen = 210;
            mMinLen = 280;
            mSecLen = 350;
            //指针多余长度
            mHourBegin = 70;
            mMinBegin = 70;
            mSecBegin = 70;
            //时间
            mHour = 0;
            mMin = 0;
            mSec = 0;
        }
    
    2.绘制表盘

    1)绘制圆
    我们可以看到整个时钟的中心始终在画布正中央,因此第一步,我们先将画布移到中间。为了获取移动到画布正中间需要的像素数,我们要先从 onSizeChange 方法中,获取图形大小,然后移动到画布中间绘制圆。

        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            mWidth = w;
            mHeight = h;
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            //移动到中心
            canvas.translate(mWidth / 2, mHeight / 2);
            //表盘
            canvas.drawCircle(0, 0, mRadio, mClockPaint);
        }
    

    2)绘制刻度
    显然,我们不能通过三角函数来确定每个刻度的位置,那样问题也变得困难。这里我们借助画布的旋转来实现。计算出最小格的角度为 6 度,因此,我们只要旋转 60 次。由于特殊位置需要有长度不同的线段标志,因此这里也需要加入判断。另外,刻度的绘制是旋转 360 度的,因此并不需要画布快照来保留当前状态。

    //刻度
    for (int i = 0; i < 60; i++) {
        int tick = i % 15 == 0 ? // 3 6 9 12 
                mBigTick : 
                (i % 5 == 0 ?  // 1 2 4 5 7 8 10 11
                        mMidTick : mSmallTick); // 分
        canvas.drawLine(0, mRadio, 0, mRadio - tick, mClockPaint);
        canvas.drawLine(0, -mRadio, 0, -mRadio + tick, mClockPaint);
        canvas.rotate(6);
    }
    

    3)绘制指针
    由于时分秒的“0”刻度在 12 的位置,因此,绘制指针的过程中,要以 12 到圆心的连线为准。

    //指针
    //时
    canvas.save();
    canvas.rotate(mHour % 12 / 12 * 360 + mMin % 60 / 60 * 30);// 计算需要转过的角度
    canvas.drawLine(0, mHourBegin, 0, -mHourLen + mHourBegin, mHourPaint);
    canvas.restore();
    //分
    canvas.save();
    canvas.rotate(mMin % 60 / 60 * 360 + mSec % 60 / 60 * 6);// 计算需要转过的角度
    canvas.drawLine(0, mMinBegin, 0, -mMinLen + mMinBegin, mMinPaint);
    canvas.restore();
    //秒
    canvas.save();
    canvas.rotate(mSec % 60 / 60 * 360);// 计算需要转过的角度
    canvas.drawLine(0, mSecBegin, 0, -mSecLen + mSecBegin, mSecPaint);
    canvas.restore();
    

    4)事件
    视图已经绘制好了,下面需要让这个视图动起来,这个比较简单,利用 Handler 就可以实现了。这里我们定义了四个状态,分别对应开始、停止、重置、继续。

    public static final int STEP_START = 0;
    public static final int STEP_STOP = 1;
    public static final int STEP_RESET = 2;
    public static final int STEP_NEXT = 3;
    

    在 handler 中处理事件:

    private boolean bContinue;
    
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case STEP_START:
                    bContinue = true;
                    sendEmptyMessage(STEP_NEXT);
                    break;
                case STEP_NEXT:
                    if (bContinue) {
                        timeUp();
                        invalidate();
                        sendEmptyMessageDelayed(STEP_NEXT, 1 * 1000);
                    }
                    break;
                case STEP_STOP:
                    bContinue = false;
                    handler.removeCallbacksAndMessages(null);
                    break;
                case STEP_RESET:
                    bContinue = true;
                    mHour = 0;
                    mMin = 0;
                    mSec = 0;
                    handler.removeCallbacksAndMessages(null);
                    sendEmptyMessage(STEP_NEXT);
                    break;
            }
        }
    };
    

    暴露出控制时间的方法:

    public void setTime(int h, int m, int s) {
        mHour = h;
        mMin = m;
        mSec = s;
        invalidate();
    }
    
    public void start() {
        handler.sendEmptyMessage(STEP_START);
    }
    
    public void stop(){
        handler.sendEmptyMessage(STEP_STOP);
    }
    
    public void reset(){
        handler.sendEmptyMessage(STEP_RESET);
    }
    

    谢谢观赏

    相关文章

      网友评论

        本文标题:自定义 View 实践(二)- 简易时钟

        本文链接:https://www.haomeiwen.com/subject/ifgzvxtx.html