基于CAShapeLayer与CABasicAnimation的环形进度条连续动画问题
解决CAShapeLayer环形动画连续调用仅显示最后一次的问题
嘿,这个问题我之前做进度环的时候也踩过坑!核心原因是你连续调用animateCircle时,第二次调用会直接覆盖掉第一次的动画设置——因为Core Animation里,当你直接修改strokeEnd这种图层属性时,会立刻更新模型层的值,同时新添加的动画会替换掉同名key的旧动画(你用了同一个@"animateCircle"作为key),导致第一次动画还没来得及执行就被冲掉了。
下面给你两种靠谱的解决方案,让动画能依次连贯执行:
方案一:给动画添加完成回调,链式执行
我们可以修改animateCircle方法,增加一个完成回调块,这样上一个动画结束后再触发下一个,完美解决顺序问题。
修改后的CircleView代码:
#import "CircleView.h" @interface CircleView() @property (nonatomic, strong) CAShapeLayer *circleLayer; @end @implementation CircleView -(instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { self.backgroundColor = UIColor.clearColor; UIBezierPath *circlePath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.frame.size.width / 2., self.frame.size.height / 2.0) radius: (self.frame.size.width - 10) / 2 startAngle:0.0 endAngle:M_PI * 2 clockwise:YES]; CAShapeLayer *downLayer = [[CAShapeLayer alloc] init]; downLayer.path = circlePath.CGPath; downLayer.fillColor = UIColor.clearColor.CGColor; downLayer.strokeColor = UIColor.lightGrayColor.CGColor; downLayer.lineWidth = 10.0; downLayer.strokeEnd = 1.0; self.circleLayer = [[CAShapeLayer alloc] init]; self.circleLayer.path = circlePath.CGPath; self.circleLayer.fillColor = UIColor.clearColor.CGColor; self.circleLayer.strokeColor = UIColor.redColor.CGColor; self.circleLayer.lineWidth = 10.0; self.circleLayer.strokeEnd = 0.0; [self.layer addSublayer:downLayer]; [self.layer addSublayer: self.circleLayer]; } return self; } // 新增completion回调参数 -(void)animateCircle:(NSTimeInterval)duration fromPart:(CGFloat)fromPart toPart:(CGFloat)toPart completion:(void(^)(void))completion { CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"]; animation.duration = duration; animation.fromValue = @(fromPart); animation.toValue = @(toPart); animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; // 保持动画结束后的状态,避免图层跳回初始值 animation.removedOnCompletion = NO; animation.fillMode = kCAFillModeForwards; // 使用CATransaction监听动画完成 if (completion) { [CATransaction begin]; [CATransaction setCompletionBlock:completion]; } [self.circleLayer addAnimation:animation forKey:@"animateCircle"]; // 更新模型层的值,确保动画结束后状态持久化 self.circleLayer.strokeEnd = toPart; if (completion) { [CATransaction commit]; } } // 额外优化:如果用AutoLayout,重写这个方法更新环形路径 -(void)layoutSubviews { [super layoutSubviews]; UIBezierPath *circlePath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.bounds.size.width / 2., self.bounds.size.height / 2.0) radius: (self.bounds.size.width - 10) / 2 startAngle:0.0 endAngle:M_PI * 2 clockwise:YES]; self.circleLayer.path = circlePath.CGPath; // 别忘了更新底层灰色环的路径 for (CALayer *layer in self.layer.sublayers) { if ([layer isKindOfClass:[CAShapeLayer class]] && layer != self.circleLayer) { ((CAShapeLayer *)layer).path = circlePath.CGPath; } } } @end
调用的时候用链式回调:
[self.circleView animateCircle:1 fromPart:0.0 toPart:0.25 completion:^{ [self.circleView animateCircle:1 fromPart:0.25 toPart:0.5 completion:nil]; }];
方案二:创建动画队列,批量执行
如果有多个进度节点要依次展示,比如下载分多个阶段,把所有动画参数放到队列里,递归执行会更省心:
在CircleView里新增队列相关的属性和方法:
@interface CircleView() @property (nonatomic, strong) CAShapeLayer *circleLayer; @property (nonatomic, strong) NSArray *animationQueue; @property (nonatomic, assign) NSInteger currentAnimIndex; @end // 新增队列执行方法 -(void)startAnimationQueue:(NSArray *)animationQueue { self.animationQueue = animationQueue; self.currentAnimIndex = 0; [self executeNextAnimation]; } -(void)executeNextAnimation { if (self.currentAnimIndex >= self.animationQueue.count) { return; } NSDictionary *animInfo = self.animationQueue[self.currentAnimIndex]; NSTimeInterval duration = [animInfo[@"duration"] doubleValue]; CGFloat from = [animInfo[@"fromPart"] floatValue]; CGFloat to = [animInfo[@"toPart"] floatValue]; [self animateCircle:duration fromPart:from toPart:to completion:^{ self.currentAnimIndex++; [self executeNextAnimation]; }]; }
调用时把所有动画参数打包成数组:
NSArray *progressQueue = @[ @{@"duration": @1, @"fromPart": @0.0, @"toPart": @0.25}, @{@"duration": @1, @"fromPart": @0.25, @"toPart": @0.5}, @{@"duration": @0.5, @"fromPart": @0.5, @"toPart": @0.75} // 可以加更多阶段 ]; [self.circleView startAnimationQueue:progressQueue];
这样不管多少个动画阶段,都能按顺序依次执行,完美模拟下载进度的逐步推进~
内容的提问来源于stack exchange,提问作者surToTheW
相关产品推荐
相关产品推荐

