绘图-圆环进度条实现详解

前言

实现了一款时下比较流行的环状进度动图,以下是源码解析


使用 Core Graphics 和 定时器 实现环形进度动图

圆环进度.gif

核心源码

# 使用  [self setNeedsDisplay];  会触发drawRect 方法达到刷新视图的效果。
//画背景线、填充线、小圆点、文字
- (void)drawRect:(CGRect)rect {
    [super drawRect:rect];
    
    
    #获取图形上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    
    #设置中心点 半径 起点及终点
    CGFloat maxWidth = self.frame.size.width<self.frame.size.height?self.frame.size.width:self.frame.size.height;
    CGPoint center = CGPointMake(maxWidth/2.0, maxWidth/2.0);
    CGFloat radius = maxWidth/2.0-_strokeWidth/2.0-1;//留出一像素,防止与边界相切的地方被切平
    CGFloat endA = _startAngle + (CircleDegreeToRadian(360) - _reduceValue);//圆终点位置
    CGFloat valueEndA = _startAngle + (CircleDegreeToRadian(360)-_reduceValue)*fakeProgress;  //数值终点位置
    
    #灰色区域背景
    UIBezierPath *basePath = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:_startAngle endAngle:endA clockwise:YES];
    //线条宽度
    CGContextSetLineWidth(ctx, _strokeWidth);
    //设置线条顶端
    CGContextSetLineCap(ctx, kCGLineCapRound);
    //线条颜色
    [_pathBackColor setStroke];
    //把路径添加到上下文
    CGContextAddPath(ctx, basePath.CGPath);
    //渲染背景线
    CGContextStrokePath(ctx);
    
    #彩色动态区域
    UIBezierPath *valuePath = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:_startAngle endAngle:valueEndA clockwise:YES];
    //设置线条顶端
    CGContextSetLineCap(ctx, kCGLineCapRound);
    //线条颜色
    [_pathFillColor setStroke];
    //把路径添加到上下文
    CGContextAddPath(ctx, valuePath.CGPath);
    //渲染数值线
    CGContextStrokePath(ctx);
    
   #画小圆点 使用是 绘制一张图片,其中图片的 Frame 中 x,y 使用到 正弦,余弦函数得到。
    if (_showPoint) {
        CGContextDrawImage(ctx, CGRectMake(CircleSelfWidth/2 + radius*cosf(valueEndA)-_strokeWidth/2.0, CircleSelfWidth/2 + radius*sinf(valueEndA)-_strokeWidth/2.0, _strokeWidth, _strokeWidth), [UIImage imageNamed:@"circle_point"].CGImage);
    }
   # 绘制中间的文字,由于定时器的持续执行刷新,就出现了,文字跳动累加的效果。
   # 其实直接在这里 修改一个全局 label 的值效果是一样的。
    if (_showProgressText) {
        //画文字
        NSString *currentText = [NSString stringWithFormat:@"%.2f%%",fakeProgress*100];
        //段落格式
        NSMutableParagraphStyle *textStyle = [[NSMutableParagraphStyle defaultParagraphStyle] mutableCopy];
        textStyle.lineBreakMode = NSLineBreakByWordWrapping;
        textStyle.alignment = NSTextAlignmentCenter;//水平居中
        //字体
        UIFont *font = [UIFont boldSystemFontOfSize:22.0];
        //构建属性集合
        NSDictionary *attributes = @{NSFontAttributeName:font, NSParagraphStyleAttributeName:textStyle};
        //获得size
        CGSize stringSize = [currentText sizeWithAttributes:attributes];
        //垂直居中
        CGRect r = CGRectMake((CircleSelfWidth-stringSize.width)/2.0, (CircleSelfHeight - stringSize.height)/2.0,stringSize.width, stringSize.height);
        [currentText drawInRect:r withAttributes:attributes];
    }
    
}

小球图片的坐标计算参照以下这张图

Paste_Image.png

#设置进度 属性的 set 方法
- (void)setProgress:(CGFloat)progress {
    
    _progress = progress;
    fakeProgress = 0.0;
    
    if (timer) {
        [timer invalidate];
        timer = nil;
    }
    
    //如果为0则直接刷新
    if (_progress == 0.0) {
        [self setNeedsDisplay];
        return;
    }
    #生成一个定时器 持续触发  drawRect 方法
    timer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(asa ) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    
}
#定时器绑定的方法
- (void)asa
{
    if (fakeProgress >= _progress || fakeProgress >= 1.0f) {
        #最后一次赋准确值 并移除定时器
        fakeProgress = _progress;
        [self setNeedsDisplay];
        
        if (timer) {
            [timer invalidate];
            timer = nil;
        }
        return;
    } else {
        #进度条动画
        [self setNeedsDisplay];
    }
    
    # fakeProgress 数值增加
    if (_animationModel == CircleIncreaseSameTime) {
        fakeProgress += 0.01*(_progress);//不同进度动画时间基本相同
    } else {
        fakeProgress += 0.01;//进度越大动画时间越长。
    }
}

使用 CAShapeLayer 和 CABasicAnimation 实现环形进度动图

</br>

进度.gif

核心源码

# 橘红色的背景
CAShapeLayer *shapeLayer11=[CAShapeLayer layer];
UIBezierPath*path12=[UIBezierPath bezierPathWithOvalInRect:CGRectMake(89, 299, 138, 138)];
# 设置末端的形状为圆形
path12.lineCapStyle = kCGLineCapRound;  
shapeLayer11.path=path12.CGPath;
# 就是图中我们看到的橘红色   CAShapeLayer的fillColor 填充的是 贝塞尔曲线的有效区域   半径为 138的圆
shapeLayer11.fillColor=[UIColor colorWithRed:0.95 green:0.37 blue:0.38 alpha:1.00].CGColor;
# 设置lineWidth 为0
shapeLayer11.lineWidth=0;
 # lineWidth 为0 strokeColor没有效果
//shapeLayer11.strokeColor=[UIColor colorWithRed:0.95 green:0.37 blue:0.38 alpha:1.00].CGColor;
[self.layer addSublayer:shapeLayer11];

# 深绿色的区域
CAShapeLayer *shapeLayer1 = [CAShapeLayer layer];
UIBezierPath *path1 = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(89, 299, 138, 138)];
path1.lineCapStyle = kCGLineJoinRound;
shapeLayer1.path = path1.CGPath;
# 设置内部填充色为 无色
shapeLayer1.fillColor = [UIColor clearColor].CGColor; 
# 设计 lineWidth 为20 (深绿色区域)我们可以看到是绿色区域的中心线在贝塞尔曲线的边界上
shapeLayer1.lineWidth = 20;
shapeLayer1.strokeColor = [UIColor colorWithRed:0.16 green:0.58 blue:0.22 alpha:1.00].CGColor;
[self.layer addSublayer:shapeLayer1];

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

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,982评论 4 60
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,275评论 25 707
  • 你是内心的想法 却是我要认知的做法 读不出来的是下一秒的时间 过去的是你对我的惩罚 情绪你在哪? 听不到你的回答 ...
    清欢的林夕阅读 203评论 2 1
  • 乡间的遗迹 鲁先圣 最近一年多的时间,常常在故乡的乡间里行走。过去,我曾经在故乡生活工作过多年,曾经自信对故乡...
    鲁先圣阅读 463评论 0 1
  • 爱的名义 你真的做到尊重了吗?你真的是在倾听吗?你真的是在全身心...
    禾雨分享阅读 379评论 2 3