最近做项目的时候,射击狮设计了一个环形的进度条用来向用户呈现产品的信息,做完之后,感觉这个效果应该挺常用的,索性就整理一下了。

思路

分析了一下效果图,其实里面主要就三个主要元素,一个白色的半透明的背景圆环,姑且称之为A吧,一个橘黄色的进度圆环,称之为B,一个现实百分比的label。圆环可以结合CAShapeLayerUIBezierPath进行绘制,那么整过过程就很简单了。关于CAShapeLayer官方的API可以查看这里

过程

为了方便我把画笔的宽度,开始和结束的位置,背景颜色定义了宏,如下图


首先我们需要创建半透明白色背景的圆环A,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
//创建背景
- (void)initBackgroundView
{
UIBezierPath *path = [UIBezierPath bezierPath];
[path addArcWithCenter:center radius:radius startAngle:ChartStart endAngle:ChartEnd clockwise:YES];
bgCircleLayer = [CAShapeLayer layer];
bgCircleLayer.path = path.CGPath;
bgCircleLayer.fillColor = [UIColor clearColor].CGColor;
bgCircleLayer.strokeColor = BackGroundColor.CGColor;
bgCircleLayer.lineWidth = ChartWidth;
[self.layer addSublayer:bgCircleLayer];
}

为了每次方便地设置环形进度,我们在.h文件中提供了一个方法:

1
- (void)setProgress:(double)value animated:(BOOL)animate;

具体的实现如下:

1
2
3
4
5
6
7
8
9
10
11
- (void)setProgress:(double)value animated:(BOOL)animate
{
NSAssert(value < 1 && value > 0, @"value是包含0、1之间的数值");
progress = value;
animated = animate;
radian = value * (2* M_PI - fabs(ChartStart - ChartEnd));
self.percentLB.text = [NSString stringWithFormat:@"%.0f%%",progress*100];
self.percentLB.center = [self percentCenter];
//创建环形进度
[self creatCircleLayer];
}

创建环形进度的方法- (void)creatCircleLayer其实和环形的背景一样的,这里就不再贴代码了。

然后就是解决环形进度动画的问题了。我们可以使用CABasicAnimation来实现,具体的代码:

1
2
3
4
5
6
7
8
- (void)circleAnimation:(CALayer*)layer
{
CABasicAnimation *basic=[CABasicAnimation animationWithKeyPath:@"strokeEnd"];
basic.duration = 2;
basic.fromValue = @(0);
basic.toValue = @(1);
[layer addAnimation:basic forKey:@"StrokeEndKey"];
}

然后就是百分比label的展示了,其实这里面也需要用到UIBezierPath来展示label的运动路径,label动画的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)percentAnimate:(CALayer*)layer
{
CGFloat R = CGRectGetWidth(self.frame)/2.0-CGRectGetWidth(self.percentLB.frame)/2.0;
CGPoint centerP = CGPointMake(CGRectGetWidth(self.frame)/2.0, CGRectGetWidth(self.frame)/2.0);

UIBezierPath *path=[UIBezierPath bezierPath];
//这段圆弧的中心,半径,开始角度,结束角度,是否顺时针方向。
[path addArcWithCenter:centerP radius:R startAngle:ChartStart endAngle:ChartStart + radian clockwise:YES];
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
animation.duration = 2.0;
animation.path = path.CGPath;
animation.calculationMode = kCAAnimationPaced;
[layer addAnimation:animation forKey:@"PercentPosition"];

}

但是我们需要确定label的最终位置,需要用到sincos三角函数,然后通过UIBezierPath的半径和中心点来label最终位置现实的位置,具体确定方法:

1
2
3
4
5
6
7
8
9
//百分比的最终的中心位置
- (CGPoint)percentCenter
{
CGFloat R = CGRectGetWidth(self.frame)/2.0-CGRectGetWidth(self.percentLB.frame)/2.0;
CGPoint centerP = CGPointMake(CGRectGetWidth(self.frame)/2.0, CGRectGetWidth(self.frame)/2.0);
CGFloat x = centerP.x + R * cos(radian + ChartStart);
CGFloat y = centerP.y + R * sin(radian + ChartStart);
return CGPointMake(x, y);
}

我们还需要做一件事,就是label运动的整过过程的text也是动画显示的,这里我为UILabel增加了一个方法,结合GCD来实现labeltext动画的,具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
- (void)scrollNumFromValue:(CGFloat)fromValue toValue:(CGFloat)toValue during:(CGFloat)time
{
//默认60次动画
self.text = [NSString stringWithFormat:@"%.0f%%",fromValue];
__block int count = 30; //循环的次数
CGFloat increase = (toValue - fromValue)/count;
CGFloat perTime = time * 1.0 / count;

__block float currentNum = fromValue;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,queue);
dispatch_source_set_timer(_timer,dispatch_walltime(NULL, 0),perTime*NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(_timer, ^{
//计算每次递增
if(count <= 0){
dispatch_source_cancel(_timer);
dispatch_async(dispatch_get_main_queue(), ^{
self.text = [NSString stringWithFormat:@"%.0f%%",toValue];
});
}else{
dispatch_async(dispatch_get_main_queue(), ^{
if (currentNum >= toValue) {
count = 0;
}else{
currentNum = currentNum + increase;
self.text = [NSString stringWithFormat:@"%.0f%%",currentNum];
}
});
count --;
}
});
dispatch_resume(_timer);

}

好了,整个环形进度条就实现了,具体的效果如下图:

本文Demo