iOS - 使用定时器与贝塞尔绘制圆形进度条

前言:

本篇主要讲解的是如何使用定时器与贝塞尔绘制圆形进度条。在此之前,我们先了解一下定时器的一些知识。

效果展示:
zhanshi.gif
目录:
  • 1. CADisplayLink 的介绍
  • 2. 项目演练思想
  • 3. 项目代码示例

1. CADisplayLink 的介绍

1.1 什么是CADisplayLink **
  
CADisplayLink是一个能让我们以和屏幕刷新率相同的频率将内容画到屏幕上的定时器。我们在应用中创建一个新的 CADisplayLink对象,把它添加到一个runloop中,并给它提供一个 targetselector** 在屏幕刷新的时候 调用 。

  一但 CADisplayLink 以特定的模式注册到runloop之后,每当屏幕需要刷新的时候,runloop就会调用CADisplayLink绑定的target上selector,这时target可以读到 CADisplayLink 的每次调用的时间戳,用来准备下一帧显示需要的数据。
  
例如:
一个视频应用使用时间戳来计算下一帧要显示的视频数据。在UI做动画的过程中,需要通过时间戳来计算UI对象在动画的下一帧要更新的大小等等。

  在添加进runloop的时候我们应该选用高一些的优先级,来保证动画的平滑。可以设想一下,我们在动画的过程中,runloop被添加进来了一个高优先级的任务,那么,下一次的调用就会被暂停转而先去执行高优先级的任务,然后在接着执行CADisplayLink的调用,从而造成动画过程的卡顿,使动画不流畅。

  duration属性提供了每帧之间的时间,也就是屏幕每次刷新之间的的时间。我们可以使用这个时间来计算出下一帧要显示的UI的数值。但是 duration只是个大概的时间,如果CPU忙于其它计算,就没法保证以相同的频率执行屏幕的绘制操作,这样会跳过几次调用回调方法的机会。

  frameInterval属性是可读可写的NSInteger型值,标识间隔多少帧调用一次selector方法,默认值是1,即每帧都调用一次。如果每帧都调用一次的话,对于iOS设备来说那刷新频率就是60HZ也就是每秒60次,如果将frameInterval设为2 那么就会两帧调用一次,也就是变成了每秒刷新30次。

  我们通过pause属性开控制CADisplayLink的运行。当我们想结束一个CADisplayLink的时候,应该调用-(void)invalidate

  从runloop中删除并删除之前绑定的** targetselector,另外CADisplayLink**不能被继承。

1.2 CADisplayLink与 NSTimer有什么不同

  iOS设备的屏幕刷新频率是固定的,CADisplayLink在正常情况下会在每次刷新结束都被调用,精确度相当高。

  NSTimer的精确度就显得低了点,比如NSTimer的触发时间到的时候,runloop如果在阻塞状态,触发时间就会推迟到下一个runloop周期。并且 NSTimer新增了tolerance属性,让用户可以设置可以容忍的触发的时间的延迟范围。

  CADisplayLink使用场合相对专一,适合做UI的不停重绘,比如自定义动画引擎或者视频播放的渲染。NSTimer的使用范围要广泛的多,各种需要单次或者循环定时处理的任务都可以使用。在UI相关的动画或者显示内容使用CADisplayLink比起用NSTimer的好处就是我们不需要在格外关心屏幕的刷新频率了,因为它本身就是跟屏幕刷新同步的。

给非UI对象添加动画效果

  我们知道动画效果就是一个属性的线性变化,比如UIView 动画的 EasyIn EasyOut 。通过数值按照不同速率的变化我们能生成更接近真实世界的动画效果。我们也可以利用这个特性来使一些其他属性按照我们期望的曲线变化。比如当播放视频时关掉视频的声音我可以通过CADisplayLink来实现一个 EasyOut的渐出效果:先快速的降低音量,在慢慢的渐变到静音。

注意:
  通常来讲:iOS设备的刷新频率事60HZ也就是每秒60次。那么每一次刷新的时间就是1/60秒 大概16.7毫秒。当我们的frameInterval值为1的时候我们需要保证的是CADisplayLink调用的`target`的函数计算时间不应该大于 16.7否则就会出现严重的丢帧现象。

  在mac应用中我们使用的不是CADisplayLink
而是** CVDisplayLink**它是基于C接口的用起来配置有些麻烦但是用起来还是很简单的。

2. 项目演练思想

演练步骤:
- 1> 自定义UIView
1. 添加到UIView上
2. 初始化UIView (bg = 黄)

     3. 画圆 底下灰色的圆(辅助圆)
        3.1 利用UIBezierPath和CAShapeLayer绘制进度条圆弧,bezierPathWithArcCenter:用这个方法,需要获知:
            (ArcCenter = ? ,radius = ?,startAngle = ?,endAngle = ?,clockwise = ?)
        3.2 由于半径需要我们自己设置,我们可以先声明一个,在进行初始化
        3.3 要利用上面方法,画圆,起点和终点的角度 (0,360)就可以画一个圆
      4. 画显示圆,滚动的 颜色为 红
        4.1 配置显示圆的CAShapeLayer,在利用CADisplayLink将显示圆的内容画到屏幕上
             (strokeColor = red)
        4.2 配置CADisplayLink(添加方法,加入运行循环,设置运行模式,默认暂停)
        4.3 响应定义器事件,绘制显示圆的路径(显示圆的路径提前要在init 中进行初始化,在这个基础上添加圆弧)
        4.4  显示圆:addArcWithCenter:==> (ArcCenter ,radius ,clockwise相同)  需要一个新的,startAngle和endAngle
               startAngle:  我们根据要求获知起点是从顶点开始的,则我们需要声明一个startAngle,初始化 - 90
                            (startAngle = (M_PI / 180.0) * _startAngle)
                endAngle: 对于终点来说,我们是在起点的基础上添加度数,由于使用定时器,我们需要_startAngle+= 3.6,来完
                           成要求,宣示圆的滚动。随着定时器,起点会等于终点,来回滚动。
                             ((M_PI / 180.0) * (_startAngle + 3.6)==/  _startAngle += 3.6;)
        5. 添加动画
           5.1 判断开启定时器startAnimation
                (如果,定时器是暂停状态,我们_startAngle = -90,开启定时器,否则~~)
        6. 配置中间显示数字的Label
            6.1 中间lab 初始化为0,滚动之后的值等于显示圆的终点
            6.2 声明一个显示的值,我一个初始化的值。
            6.3 在定时器的响应时间中我们要判断,当我们初始化的值也就是开始的值大于或等于显示的值,我们才可以
                创建显示圆的路径,并且定时器暂停,否则,_startRate ++;以达到同步绘制.
            6.4 设置动画的时候,_startRate = 0;
            6.5 设置显示值的范围 (写setRate:方法)
                           <= 0 / > 100  ==> rate = 100;
                           else { _rate = rate }
            6.6 给lab 赋值(赋值  只能是0~100)
/****************************************************************************************************************************/
 使用:
    CustomCircleView *circleView = [[CustomCircleView alloc]initWithFrame:(CGRect){(self.view.bounds.size.width - 100) * 0.5,100,100,100}];
      _circleView.rate =?; (必须为纯数字0~100)
     [self.view addSubview:_circleView];
>                               
//   为什莫+ 3.6    ,100 X3.6 = 360  lab 与绘图同步

3. 项目代码示例

CustomCircleView.h
#import <UIKit/UIKit.h>

@interface CustomCircleView : UIView
// 中间显示的数字
@property (nonatomic, assign) NSInteger rate;
// 开始动画
- (void)startAnimation;
@end

CustomCircleView.m
#import "CustomCircleView.h"

#define LineWidth 4
@interface CustomCircleView ()
{
    CGFloat _startAngle; // 开始的角度
    NSInteger _startRate;
}
//   半径r
@property(nonatomic,assign) CGFloat rWidth;
//    显示圆的边缘图层
@property(nonatomic,strong) CAShapeLayer *shapeLayer;
//    定时器
@property(nonatomic,strong) CADisplayLink *displayLink;
//     显示圆的路径
@property(nonatomic,strong) UIBezierPath *bPath;
//      显示Lab
@property (nonatomic, strong) UILabel *rateLbl;
@end
@implementation CustomCircleView
-(instancetype)initWithFrame:(CGRect)frame
{
      self =  [super initWithFrame:frame];
   if (self) {
        _startAngle = -90; // 从圆的最顶部开始
        _rWidth = frame.size.width;
        _bPath = [UIBezierPath bezierPath];
        // 先画一个底部的圆
        [self configBgCircle];
        // 配置CAShapeLayer
        [self configShapeLayer];
        // 配置CADisplayLink
        [self configDisplayLink];
        // label
        [self configLab];
    }
    return self;
>
}
#pragma mark - 底下灰色的圆(辅助圆)
- (void)configBgCircle
{
    UIBezierPath *bPath = [UIBezierPath bezierPathWithArcCenter:(CGPoint){self.bounds.size.width *0.5,self.bounds.size.height *0.5} radius:_rWidth * 0.5 startAngle:0 endAngle:360 clockwise:YES];
     CAShapeLayer *shaperLayer = [CAShapeLayer layer];
     shaperLayer.lineWidth = LineWidth;
     shaperLayer.strokeColor = [UIColor lightGrayColor].CGColor;
     shaperLayer.fillColor = nil;
     shaperLayer.path = bPath.CGPath;
     [self.layer addSublayer:shaperLayer];
}
#pragma mark 配置CAShaperLayer(用于显示圆)
- (void)configShapeLayer
{
    _shapeLayer = [CAShapeLayer layer];
    _shapeLayer.lineWidth = LineWidth;
    _shapeLayer.strokeColor = [UIColor redColor].CGColor;
    _shapeLayer.fillColor = nil;
    [self.layer addSublayer:_shapeLayer];
}
#pragma mark 配置CADisplayLink
- (void)configDisplayLink
{
    _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawCircle)];
    [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    _displayLink.paused = YES; //  默认暂停
}
#pragma mark - 中间显示数字的Label
- (void)configLab
{
    CGFloat rateLabX = 10;
    CGFloat rateLabW = self.frame.size.width - 2 * rateLabX;
    CGFloat rateLabH = 40;
    CGFloat rateLabY = (self.frame.size.height - rateLabH) * 0.5;
    UILabel *lab = [[UILabel alloc] initWithFrame:CGRectMake(rateLabX, rateLabY, rateLabW, rateLabH)];
    _rateLbl = lab;
    lab.textAlignment = NSTextAlignmentCenter;
    lab.textColor = [UIColor blackColor];
    lab.text = @"0%";
    [self addSubview:lab];
}
#pragma mark - event response
- (void)drawCircle
{
    if (_startRate >= _rate) {
        _bPath = [UIBezierPath bezierPath];
        _displayLink.paused = YES;
        return;
     }
  _startRate ++;
  _rateLbl.text = [NSString stringWithFormat:@"%ld%%",_startRate];
    [_bPath addArcWithCenter:CGPointMake(self.frame.size.width * 0.5, self.frame.size.height * 0.5) radius:_rWidth * 0.5  startAngle:(M_PI /180.0) *_startAngle endAngle:(M_PI /180.0) *(_startAngle + 3.6) clockwise:YES];
    _shapeLayer.path = _bPath.CGPath;
    _startAngle += 3.6;
    }
#pragma mark - public methods
- (void)startAnimation
{
if (_displayLink.paused == YES) {
    _startAngle = -90;
    _startRate = 0;
    _displayLink.paused = NO;
}
}
#pragma mark - getter/setter
- (void)setRate:(NSInteger)rate
{
    if (rate <= 0 || rate >100) {
        rate = 100;
    }else{
     _rate = rate;
    }
}
@end

总结,是一个学习的过程,虽然仍很迷茫,希望遇伯乐,北京。

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

推荐阅读更多精彩内容