OC 圆弧渐变进度条的实现
由于项目需要一个环形渐变进度条显示课程,这方便网上的确有很多相关资料但是,都是比较零散的而且,大多数只是放一堆代码就算完了。这里我想详细写一篇我自己实现这个进度条的过程。
实现一个圆弧进度条主要分为三步
一、画圆弧这里用的贝赛尔曲线,就是这个东西:UIBezierPath
二、根据贝塞尔曲线路径画两个圆弧一个底色一个上面的填充色,用到的是这个类CAShapeLayer.h
三、画两个渐变色块,把上面的进度条路径映射到渐变色块上,渐变色块用的是这个东西CAGradientLayer.h
目标效果如图
第一步:
- 我们把图案分解开来,就是一个圆环上面叠加一个圆环,底色的圆环是灰色,而上面的圆环是自定义颜色的。
-
首先我们画一个圆环,其实就是一个粗的圆形,这里用UIBezierPath来画。
效果图片:
-
代码:
//贝塞尔曲线画圆弧
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的官方文档中这样规定:
所以我要画出目标圆弧就需要从 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];
效果图:
第二步:
- 根据第一步画好的路径通过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];
效果图:
- 画出进度条圆弧
_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属性 让进度不是全满的,效果图如下:
第三步:
以上完成了基础的两步。现在就是最麻烦的渐变色这一块了。首先熟悉下处理渐变色的那个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];
看一下效果图
修改下这个方法
[leftGradientLayer setLocations:@[@0,@0.5]];
效果图:
只有一边渐变上半部分全红。
再修改下这个属性 主要控制渐变色方向
[leftGradientLayer setLocations:@[@0,@1]];
[leftGradientLayer setStartPoint:CGPointMake(0, 1)];
[leftGradientLayer setEndPoint:CGPointMake(1, 0)];
这样就是对角线的渐变 效果图如下:
所以简单点就是这样做渐变色的环 先设置宽高和环一样如下图
然后再加下面这条代码 就能把渐变色图层颜色映射到环的layer上面
[self.gradientLayer setMask:_shapeLayer];
效果图
但是这里其实有个问题,当圆弧进度满的时候就能看到如下图:
可以看到上面图片的右下角并没有那么红所以不是真正的渐变。那么到底如何做到真正的渐变圆环呢?
其实上面刚开始画渐变图层的时候我就埋了个伏笔。
而且煞费苦心的测试了渐变图层的那几个属性,其实就是为了如下的效果:
然后映射到圆弧上如下效果图:
到这里其实还有个问题就是顶部过度会有一个明显的断层
所以我们就要用到[leftGradientLayer setLocations:@[@0,@0.5]];这个属性了 设置渐变色范围。让顶部渐变色基本不动
左边的渐变色块:
[leftGradientLayer setLocations:@[@0,@0.9]];
右边的渐变色块:
[rightGradientLayer setLocations:@[@0.1, @1]];
再看看效果图
动画实现
通过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;
}