iOS圆弧渐变进度条的实现

由于项目需要一个环形渐变进度条显示课程,这方便网上的确有很多相关资料但是,都是比较零散的而且,大多数只是放一堆代码就算完了。这里我想详细写一篇我自己实现这个进度条的过程。

实现一个圆弧进度条主要分为三步
一、画圆弧这里用的贝赛尔曲线,就是这个东西:UIBezierPath
二、根据贝塞尔曲线路径画两个圆弧一个底色一个上面的填充色,用到的是这个类CAShapeLayer.h
三、画两个渐变色块,把上面的进度条路径映射到渐变色块上,渐变色块用的是这个东西CAGradientLayer.h

目标效果如图


AirPlay_Movie_2017-11-29_04-42-48.gif

第一步:

  • 我们把图案分解开来,就是一个圆环上面叠加一个圆环,底色的圆环是灰色,而上面的圆环是自定义颜色的。
    • 首先我们画一个圆环,其实就是一个粗的圆形,这里用UIBezierPath来画。
      效果图片:


      黑色圆环路径.jpg

      代码:

    //贝塞尔曲线画圆弧
      UIBezierPath *circlePath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.width / 2, self.height / 2) radius:(self.width - 20)/2 startAngle:0 endAngle:2 * M_PI clockwise:YES];
      
      //设置颜色
      [[UIColor blackColor] set];
      
      //线粗细
      circlePath.lineWidth = 10;
      
      //开始绘图
      [circlePath stroke];
    
    

这里主要要注意这个方法:

+ (instancetype)bezierPathWithArcCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise;

五个参数分别为
center:圆心
radius:半径
startAngle:起始角度
endAngle:终止角度
clockwise:是否顺时针画图
主要难理解的是startAngle 和 endAngle

在OC的官方文档中这样规定:
官方文档画圆弧.jpg

所以我要画出目标圆弧就需要从 3π/4 开始到 1π/4结束
方法改为:

UIBezierPath *circlePath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.width / 2, self.height / 2) radius:(self.width - 20)/2 startAngle:M_PI / 4 + M_PI / 2 endAngle:M_PI / 4 clockwise:YES];

效果图:


黑色圆弧路径.jpg

第二步:

  • 根据第一步画好的路径通过CAShapeLayer 画到layer层上去
    先画灰色一圈:
    CAShapeLayer *bgLayer = [CAShapeLayer layer];
    bgLayer.frame = self.bounds;
    bgLayer.fillColor = [UIColor clearColor].CGColor;//填充色 -  透明
    bgLayer.lineWidth = 20.f;
    bgLayer.strokeColor = ZCCRGBColor(212, 212, 212, 1.0).CGColor;//线条颜色
    bgLayer.strokeStart = 0;//起始点
    bgLayer.strokeEnd = 1;//终点 
    bgLayer.lineCap = kCALineCapRound;//让线两端是圆滑的状态
    bgLayer.path = circlePath.CGPath;//这里就是把背景的路径设为之前贝塞尔曲线的那个路径
    [self.layer addSublayer:bgLayer];

效果图:


弧形进度条底色.jpg
  • 画出进度条圆弧
_shapeLayer = [CAShapeLayer layer];
    _shapeLayer.frame = self.bounds;
    _shapeLayer.fillColor = [UIColor clearColor].CGColor;
    _shapeLayer.lineWidth = 20.f;
    _shapeLayer.lineCap = kCALineCapRound;
//    _shapeLayer.strokeColor = color.CGColor;
    _shapeLayer.strokeColor = [UIColor blueColor].CGColor;
    _shapeLayer.strokeStart = 0;
    _shapeLayer.strokeEnd = 0.8;
    _shapeLayer.path = circlePath.CGPath;
    [self.layer addSublayer:_shapeLayer];

代码和上面大致一样就是改一下颜色还有 strokenEnd属性 让进度不是全满的,效果图如下:
蓝色进度条.jpg

第三步:
以上完成了基础的两步。现在就是最麻烦的渐变色这一块了。首先熟悉下处理渐变色的那个layer类,CAGradientLayer这个也是layer的子类,我这直接那例子讲吧

//初始化一个渐变图层
    CAGradientLayer *leftGradientLayer = [CAGradientLayer layer];
//设frame
    leftGradientLayer.frame = CGRectMake(0, 0, self.width / 2, self.height);
//设渐变颜色 
//ZCCRGBColor是我自定义的宏 #define ZCCRGBColor(a,b,c,al) [UIColor colorWithRed:a/255.0 green:b/255.0 blue:c/255.0 alpha:al] 
    [leftGradientLayer setColors:[NSArray arrayWithObjects:(id)ZCCRGBColor(255, 255, 0, 1).CGColor, (id)ZCCRGBColor(255, 0, 0, 1).CGColor, nil]];
//这里设置渐变色渐变范围 0到1就是整个leftGradientLayer上都在渐变
    [leftGradientLayer setLocations:@[@0,@1]];
//下面这两个就是渐变色方向Y越大就是越下面 所以是从下到上从黄到红渐变
    [leftGradientLayer setStartPoint:CGPointMake(0, 1)];
    [leftGradientLayer setEndPoint:CGPointMake(0, 0)];
添加到父图层
    [_gradientLayer addSublayer:leftGradientLayer];

看一下效果图
左边从下到上渐变图层.jpg

修改下这个方法

 [leftGradientLayer setLocations:@[@0,@0.5]];

效果图:


只有一半是渐变的.jpg

只有一边渐变上半部分全红。

再修改下这个属性 主要控制渐变色方向

     [leftGradientLayer setLocations:@[@0,@1]];
    [leftGradientLayer setStartPoint:CGPointMake(0, 1)];
    [leftGradientLayer setEndPoint:CGPointMake(1, 0)];

这样就是对角线的渐变 效果图如下:


对角线渐变.jpg

所以简单点就是这样做渐变色的环 先设置宽高和环一样如下图


渐变色整块.jpg

然后再加下面这条代码 就能把渐变色图层颜色映射到环的layer上面

[self.gradientLayer setMask:_shapeLayer];

效果图


对角线渐变色圆弧.jpg

但是这里其实有个问题,当圆弧进度满的时候就能看到如下图:


对角线渐变圆弧全.jpg

可以看到上面图片的右下角并没有那么红所以不是真正的渐变。那么到底如何做到真正的渐变圆环呢?

其实上面刚开始画渐变图层的时候我就埋了个伏笔。

而且煞费苦心的测试了渐变图层的那几个属性,其实就是为了如下的效果:
左右渐变图层.jpg

然后映射到圆弧上如下效果图:
左右渐变块映射的圆弧.jpg

到这里其实还有个问题就是顶部过度会有一个明显的断层
所以我们就要用到[leftGradientLayer setLocations:@[@0,@0.5]];这个属性了 设置渐变色范围。让顶部渐变色基本不动

左边的渐变色块:
[leftGradientLayer setLocations:@[@0,@0.9]];
右边的渐变色块:
[rightGradientLayer setLocations:@[@0.1, @1]];

再看看效果图


图片.png

好了 这就差不多完成一个渐变色圆弧了,如果强迫症的朋友其实可以弄四个渐变色块从 左下→左上→右上→右下 依次渐变再映射,那样就会更加完美。

终于写完了。。新手第一次写这样的教学文 有什么错误的地方还请多包涵指出,不懂得可以留言问我

最后,动画是怎么实现的呢??
是通过timer 设置_shapeLayer.strokeEnd这个 strokEnd属性实现的
以下是具体代码~

- (void)animateToProgress:(CGFloat)progress{
    
//    NSLog(@"增加到progress%lf", progress);
    
    if(_shapeLayer.strokeEnd != 0){
        [self animateToZero];
    }
    
    __weak typeof(self)weakSelf = self;
    
    NSLog(@"-----%lf",_shapeLayer.strokeEnd);
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_shapeLayer.strokeEnd * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    
        [weakSelf deleteTimer];
        
        NSString *progressStr = [NSString stringWithFormat:@"%lf",progress];
        
        NSDictionary *userInfo = @{@"progressStr":progressStr};
        
        weakSelf.timer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:weakSelf selector:@selector(animate:) userInfo:userInfo repeats:YES];
    });
    
}

- (void)animate:(NSTimer *)time{
    
    CGFloat progress = [[time.userInfo objectForKey:@"progressStr"]  floatValue];
    
    if(_shapeLayer.strokeEnd <= progress)
    {
        _shapeLayer.strokeEnd += 0.01;
    }else{
        [self deleteTimer];
    }
}
//回滚到0  先判断 timer 有没有存在 存在 就把timer 删除
- (void)animateToZero{
    
//    NSLog(@"删除到0");
    
    [self deleteTimer];
    
    self.timer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(animateReset) userInfo:nil repeats:YES];
}

- (void)animateReset{
    
    if(_shapeLayer.strokeEnd > 0){
        _shapeLayer.strokeEnd -= 0.01;
    }else{
        [self deleteTimer];
    }
    
}

- (void)deleteTimer{
    [self.timer invalidate];
    self.timer = nil;
}

完整代码gitHub链接

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

推荐阅读更多精彩内容