iOS-贝塞尔画圆动画(圆圈进度百分比)
目标效果
实现
新建基于UIview的文件
.h
属性
/** 0 < accuracy < 1 */ @property (nonatomic,assign) double accuracy;
.m
属性
///画圆Layer @property (nonatomic,strong) CAShapeLayer *shapeLayer; ///底层灰色圆Layer @property (nonatomic,strong) CAShapeLayer *shapeFloorLayer; ///画圆Path @property (nonatomic,strong) UIBezierPath *proPath; @property (nonatomic,strong) UILabel * accuracyLabel; @property (nonatomic,strong) UICountingLabel * aValueLabel; ///圆心 @property (nonatomic,assign) CGPoint roundCenter; ///圆半径 @property (nonatomic, assign) CGFloat radius;
方法
- (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self setup]; } return self; } - (instancetype)init { self = [super init]; if (self) { [self setup]; } return self; } - (void)setup{ self.backgroundColor = [UIColor whiteColor]; _roundCenter = CGPointMake(self.mqb_width * 0.5, self.mqb_height * 0.5); _radius = (self.mqb_width - 20*mqbScale)/2; [self addSubview:self.aValueLabel]; self.aValueLabel.sd_layout. leftSpaceToView(self, 30*mqbScale). rightSpaceToView(self, 30*mqbScale). centerYIs(_roundCenter.y - 10*mqbScale). heightIs(36*mqbScale); [self addSubview:self.accuracyLabel]; self.accuracyLabel.sd_layout. leftSpaceToView(self, 30*mqbScale). rightSpaceToView(self, 30*mqbScale). topSpaceToView(self.aValueLabel, 15*mqbScale). heightIs(20*mqbScale); self.accuracyLabel.text = @"百分比"; [self.layer addSublayer:self.shapeFloorLayer]; [self.layer addSublayer:self.shapeLayer]; } - (void)setAccuracy:(double)accuracy{ if (accuracy < 0) { _accuracy = 0; }else if (accuracy > 1){ _accuracy = 1; } else { _accuracy = accuracy; } [self startAccuracyAnimation]; [self.aValueLabel countFrom:0 to:(_accuracy*100) withDuration:0.5]; } - (void)startAccuracyAnimation{ [self.proPath addArcWithCenter:_roundCenter radius:_radius startAngle:3 * M_PI/2 endAngle:3 * M_PI/2 + 2 * M_PI * _accuracy clockwise:YES]; self.shapeLayer.path = self.proPath.CGPath; /** 动画1 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"]; animation.fromValue = @(0.0f); animation.toValue = @(1.0f); animation.duration = 0.5f; [self.shapeLayer addAnimation:animation forKey:@"animationStrokeEnd"]; */ /** 动画2 POPSpringAnimation *popStrokeEnd = [POPSpringAnimation animationWithPropertyNamed:kPOPShapeLayerStrokeEnd]; popStrokeEnd.fromValue = @(0.0f); popStrokeEnd.toValue = @(1.0f); popStrokeEnd.springBounciness = 12; popStrokeEnd.springSpeed = 12; [self.shapeLayer pop_addAnimation:popStrokeEnd forKey:@"popAnimationStrokeEnd"]; */ /** 动画3 POPSpringAnimation *popStrokeEnd = [POPSpringAnimation animationWithPropertyNamed:kPOPShapeLayerStrokeEnd]; popStrokeEnd.fromValue = @(0.5f); popStrokeEnd.toValue = @(1.0f); popStrokeEnd.springBounciness = 12; popStrokeEnd.springSpeed = 12; [self.shapeLayer pop_addAnimation:popStrokeEnd forKey:@"popAnimationStrokeEnd"]; POPSpringAnimation *popStrokeStart = [POPSpringAnimation animationWithPropertyNamed:kPOPShapeLayerStrokeStart]; popStrokeStart.fromValue = @(0.5f); popStrokeStart.toValue = @(0.0f); popStrokeStart.springBounciness = 12; popStrokeStart.springSpeed = 12; [self.shapeLayer pop_addAnimation:popStrokeStart forKey:@"popAnimationStartEnd"]; */ } #pragma mark ========== 变量 ========== - (UILabel *)accuracyLabel{ if (!_accuracyLabel) { _accuracyLabel = [[UILabel alloc]init]; _accuracyLabel.font = mqbFont(15.0); _accuracyLabel.textColor = [UIColor mqb_colorBlack]; _accuracyLabel.textAlignment = NSTextAlignmentCenter; } return _accuracyLabel; } - (UICountingLabel *)aValueLabel{ if (!_aValueLabel) { _aValueLabel = [[UICountingLabel alloc]init]; _aValueLabel.font = mqbMediumFont(40); _aValueLabel.textColor = [UIColor mqb_colorBlack]; _aValueLabel.textAlignment = NSTextAlignmentCenter; _aValueLabel.format = @"%d%%"; } return _aValueLabel; } - (CAShapeLayer *)shapeFloorLayer{ if (!_shapeFloorLayer) { ///细线条 UIColor *colorLine = [UIColor mqb_colorSeparatorColor]; UIBezierPath *pathLine = [UIBezierPath bezierPathWithArcCenter:_roundCenter radius:_radius startAngle:0 endAngle:2 * M_PI clockwise:YES]; _shapeFloorLayer = [CAShapeLayer layer]; _shapeFloorLayer.fillColor = [UIColor clearColor].CGColor; _shapeFloorLayer.strokeColor = colorLine.CGColor; _shapeFloorLayer.lineWidth = 2*mqbScale; _shapeFloorLayer.path = pathLine.CGPath; } return _shapeFloorLayer; } - (CAShapeLayer *)shapeLayer{ if (!_shapeLayer) { _shapeLayer = [CAShapeLayer layer]; _shapeLayer.strokeColor = [UIColor mqb_colorGreen].CGColor; _shapeLayer.fillColor = [UIColor clearColor].CGColor; _shapeLayer.lineWidth = 10*mqbScale;; _shapeLayer.fillRule = kCAFillRuleEvenOdd; _shapeLayer.lineCap = kCALineCapRound; } return _shapeLayer; } - (UIBezierPath *)proPath{ if (!_proPath) { _proPath = [[UIBezierPath alloc]init]; _proPath.lineWidth = 10*mqbScale; _proPath.lineCapStyle = kCGLineCapRound; _proPath.lineJoinStyle = kCGLineJoinRound; } return _proPath; }
备注
代码中用到了SDAutoLayout约束,pop 动画、UICountingLabel 第三方 label 数字动画,UICountingLabel 的 format 原本不支持的百分比格式,如若需要可在源码中 setTextValue 修改:
修改前
- (void)setTextValue:(CGFloat)value { if (self.attributedFormatBlock != nil) { self.attributedText = self.attributedFormatBlock(value); } else if(self.formatBlock != nil) { self.text = self.formatBlock(value); } else { // check if counting with ints - cast to int if([self.format rangeOfString:@"%(.*)d" options:NSRegularExpressionSearch].location != NSNotFound || [self.format rangeOfString:@"%(.*)i"].location != NSNotFound) { self.text = [NSString stringWithFormat:self.format,(int)value]; } else { self.text = [NSString stringWithFormat:self.format,value]; } } }
修改后
- (void)setTextValue:(CGFloat)value { if (self.attributedFormatBlock != nil) { self.attributedText = self.attributedFormatBlock(value); } else if(self.formatBlock != nil) { self.text = self.formatBlock(value); } else { // check if counting with ints - cast to int if([self.format rangeOfString:@"%(.*)d%%" options:NSRegularExpressionSearch].location != NSNotFound || [self.format rangeOfString:@"%(.*)i%%"].location != NSNotFound || [self.format rangeOfString:@"%(.*)d" options:NSRegularExpressionSearch].location != NSNotFound || [self.format rangeOfString:@"%(.*)i"].location != NSNotFound) { self.text = [NSString stringWithFormat:self.format,(int)value]; } else { self.text = [NSString stringWithFormat:self.format,value]; } } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】