iOS之quartz2D绘制钟表走时

作者: 请输入账号名 | 来源:发表于2017-05-22 22:50 被阅读370次

    写在前面

    Quartz2D是苹果提供的一套高效绘图的引擎,不是框架。。里面可以自定义许多的控件,一般的控件都是继承自UI~,而使用Quartz2D可以做出我们自己想要的类型的控件出来。这里就试着用Quartz2D来画一个时钟走表的控件。

    关于Quartz2D的绘制的步骤

    绘制Quartz2D一般分为以下几个步骤:

    • 1.上下文的获取 (获取或者创建都是以UIGraphics开头)
      上下文已经是创建好了的,只要获取就可以了。而且只有当系统调用-drawRect:这个方法的时候,系统才会去创建上下文,如果就单纯的手动调用-drawRect:这个方法,是不会去创建上下文,这一点很重要。
    • 2.绘制路径
      这个路径也就是相当于我们用笔在一张白纸上画的内容,包括了内容的颜色,是直线还是曲线等状态。
    • 3.把绘制的内容保存到上下文中
      我们用笔在纸张上画图,直接就显示出来我们所画的内容了,而计算机没那么智能,所以我们要手动的保存到上下文中。
    • 4.把上下文内容显示渲染到view的layer上去(stroke/fill)
      这一步就是把画的内容显示到需要展示的view中,这里展示的是view的layer层上。其实还可以展示到PDF中,printer(打印机),bitmap(一个绘图工具)等。

    0.界面相关

    既然是自定义的view,那么久要创建一个view继承自UIView,然后再view中去重写- drawRect: 这个方法。然后回到控制器去创建view,并且展示出来。

    1.绘制表盘

    1.1绘制圆,

        CGContextRef ctx = UIGraphicsGetCurrentContext();
        CGPoint center = CGPointMake(rect.size.width * 0.5, rect.size.height * 0.5);
        CGFloat radius = rect.size.width * 0.5 - 10;
        // 圆
        UIBezierPath *circle = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(center.x - radius, center.y - radius, radius * 2, radius * 2)];
        [circle setLineWidth:3.0f];
        [[UIColor lightGrayColor] set];
        CGContextAddPath(ctx, circle.CGPath);
        CGContextStrokePath(ctx);
    

    这就把圆绘制好了。

    1.2绘制刻度

    表盘上的刻度是每6°绘制一次的。我们这里需要把刻度那根直线的一个顶点定位到圆环上,然后另一个顶点根据是不是5的倍数来定。
    首先就要确定刻度线在圆上的顶点:
    都知道圆的标准公式为:

    (x-a)^2+ (y-b)^2 = r^2; 
    

    我们可以得知x与y的值:

    // 这是按照笛卡尔的坐标系的计算的结果,并不是以iOS的坐标系的结果。
    // angle代表的是角度
    x = radius * cos(angle) ; 
    y = radius * sin(angle);
    

    转换成iOS的坐标系:

    CGFloat x = center.x + radius * cos(angle) ;
    CGFloat y = center.y + radius * sin(angle);
    

    这个是在圆上的点,那么同理我们可以计算另一个点,只是修改下半径就可以得到。

    // 一般的短刻度线
    CGFloat x0 = center.x + (radius - 10) * cos(angle); 
    CGFloat y0 = center.y + (radius - 10) * sin(angle);
    // 整点时的长刻度线        
    CGFloat x1 = center.x + (radius - 20) * cos(angle);
    CGFloat y1 = center.y + (radius - 20) * sin(angle);
    

    这里的angle传入的是以-90°开始的角度,也就是12点的位置开始的角度。
    CGFloat angle = -M_PI_2 + i / 60.0f * M_PI * 2;
    依次循环添加到表盘上。
    最后展示的效果如下:

    表盘样式

    2.绘制指针

    秒针的绘制基本思路还是画一条直线,然后根据时间的来进行修改。

    2.1定时器

    这里就需要用到定时器了,需要重复执行绘制图形的操作,所以首先在控制器中添加定时器。

        self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0f repeats:YES block:^(NSTimer * _Nonnull timer) {
            [self catchTime];
        }];
        [self.timer fire];
    

    一定要注意的是在页面销毁的时候,也需要把定时器进行销毁。

    2.2秒针、分针、时针

    指针的起点在圆心出,终点可以在圆上或不在圆上,我在这里设置的不在圆上,因为有刻度线,所以留有一定的距离。

    // 起点
    CGPoint center = CGPointMake(rect.size.width * 0.5, rect.size.height * 0.5);
    [path moveToPoint:center];
    

    需要注意的是这里的point需要指明的是这个view所在的中心点,如果直接写self.view.center,这样会找到这个自定义view的父控件上去,到时候绘制出来的效果可能有偏差。

        // 终点
        // 每一次的角度。-M_PI_2 起始角度,也就是12点的位置。
        CGFloat angle = -M_PI_2 + self.seconds / 60.0 * M_PI * 2; // 外部传入的时间。。秒
        // 圆上的点
        CGFloat radius = rect.size.width * 0.5 - 15; // 秒针的长度
        CGFloat x = center.x + radius * cos(angle) ;
        CGFloat y = center.y + radius * sin(angle);
        [path addLineToPoint:CGPointMake(x, y)];
    

    分针和时针的做法同上。
    这样就把三条指针所在的线绘制出来了。


    指针

    3.走时

    3.1定时器的选择

    关于定时器,可以采用NSTimer方式或者是CADisplayLink,然后加入到NSRunloop中去。

        // 方式一:
        self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0f repeats:YES block:^(NSTimer * _Nonnull timer) {
    //        [self catchTime];
            [self catchTimeDict];
        }];
        [self.timer fire];
    
    // 方式二:
    CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(catchTime)];
    [link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
    

    方式二的刷新频率(1/60)过快,所以演示起来有点辣眼睛。

    3.2刷新并走时

    最后让这个指针动起来,所以就应该在控制器中去调用这个自定义的view。指示当前的时间,需要外部传入,也就是说自定义的view需要定义三个属性,让外部传入时分秒。

    /* sencond*/
    @property (nonatomic, assign) int seconds;
    
    /* minute*/
    @property (nonatomic, assign) int minute;
    
    /* hour*/
    @property (nonatomic, assign) int hour;
    

    计算时分秒,进行调用:

        // 方法一:
        NSString *timeStamp = [NSString stringWithFormat:@"%f", [[NSDate date] timeIntervalSince1970]];
        self.wjClockView.seconds = [WJTime wjTimeSecondsWithTimeStamp:timeStamp];
        self.wjClockView.minute = [WJTime wjTimeMinuteWithTimeStamp:timeStamp];
        self.wjClockView.hour = [WJTime wjTimeHourWithTimeStamp:timeStamp] - 12; // 这里是标注的是12小时制,所以要减去12.
        self.wjTimeLable.text = [NSString stringWithFormat:@"%02d:%02d:%02d", self.wjClockView.hour + 12, self.wjClockView.minute, self.wjClockView.seconds]; // 这里是在标签中显示24小时制,所以加12
    
        // 方法二
        NSString *timeStamp = [NSString stringWithFormat:@"%f", [[NSDate date] timeIntervalSince1970]];
        NSDictionary *timeDict = [WJTime wjTimeGetTimeDictWithTimeStamp:timeStamp];
        NSNumber *seconds = timeDict[@"seconds"];
        NSNumber *minute = timeDict[@"minute"];
        NSNumber *hour = timeDict[@"hour"];
        self.wjClockView.seconds = seconds.intValue;
        self.wjClockView.minute = minute.intValue;
        self.wjClockView.hour = hour.intValue - 12;
        self.wjTimeLable.text = [NSString stringWithFormat:@"%02d:%02d:%02d", self.wjClockView.hour + 12, self.wjClockView.minute, self.wjClockView.seconds];
    

    - wjTimeSecondsWithTimeStamp : 这些方法是我自己写的一些类,专门返回现在时刻的时分秒的,只要传入当前的时间戳就可以了。
    这样就可以显示当前时间的效果,还有一点问题就是,这个虽然调用了这个方法,但是不走时。
    解决这个问题,我们可以在自定义view中进行重写seconds属性的setter方法,然后看看手动调用-drawRect:方法,看这个方法能否实现。

    // 重写setter方法
    - (void)setSeconds:(int)seconds {
        _seconds = seconds;
        [self drawRect:self.frame]; // 不可行方式
    }
    

    但是很遗憾,并不能实现。
    因为在开篇的时候就讲了,调用-drawRect :方法时,如果是系统调用的话,那么会自动创建一个上下文,只要获取就可以了,但是如果是手动调用的话,就不会创建这个上下文,也就是说没法进行重绘。所以这种手动调用的方法失效。
    其实调用系统提供的另一个方法可以实现:- setNeedsDisplay,这个方法,系统会自动的去调用-drawRect :这个方法,也就是说系统也会自动的去创建上下文,所以可以进行重绘制图形,而-setNeedsDisplay在调用-drawRect :方法的时候,也不是马上去调用修改的,而是在建立一个flag标识,当屏幕刷新的时候进行调用-drawRect :这个方法,而屏幕刷新是在什么时候去刷新,屏幕刷新会在1s中刷新60次。

    //  重写setter方法
    - (void)setSeconds:(int)seconds {
        _seconds = seconds;
        [self setNeedsDisplay]; // 可行方式
    }
    

    以上就可以发现走时功能实现了。
    但是以上的走时,总感觉是一顿一顿的。分针和时针的走时不是那么顺滑,所以这里也需要对minute和hour属性的setter重写。
    而且对于分针和时针转动的角度,也是需要修改,为的是在走时的时候,分针和时针比较顺滑的走动,而不是走到秒针走了一圈,分针才开始跳一格。

    // 分针的角度
    CGFloat angle = -M_PI_2 + (self.minute / 60.0) * M_PI * 2 + self.seconds / 3600.f * M_PI * 2;
    // 时针的角度
    CGFloat angle = -M_PI_2 + (self.hour / 12.0) * M_PI * 2 + self.minute / 720.f * M_PI * 2 + self.seconds / (12 * 3600.f) * M_PI * 2;
    

    到这一步就是基本上完成了钟表走时的功能了。
    添加logo .

    demo传送门

    相关文章

      网友评论

        本文标题:iOS之quartz2D绘制钟表走时

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