ZZCircleProgress.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. //
  2. // ZZCircleProgress.m
  3. // ZZCircleProgressDemo
  4. //
  5. // Created by 周兴 on 2018/5/16.
  6. // Copyright © 2018年 zhouxing. All rights reserved.
  7. //
  8. #import "ZZCircleProgress.h"
  9. @interface ZZCircleProgress ()<CAAnimationDelegate>
  10. @property (nonatomic, strong) CAShapeLayer *backLayer;
  11. @property (nonatomic, strong) CAShapeLayer *progressLayer;
  12. @property (nonatomic, assign) CGFloat realWidth;//实际边长
  13. @property (nonatomic, assign) CGFloat radius;//半径
  14. @property (nonatomic, assign) CGFloat lastProgress;/**<上次进度 0-1 */
  15. @property (nonatomic, strong) CAAnimation *lastPointAnimation;
  16. @end
  17. @implementation ZZCircleProgress
  18. - (instancetype)init {
  19. if (self = [super init]) {
  20. [self initialization];
  21. }
  22. return self;
  23. }
  24. - (instancetype)initWithFrame:(CGRect)frame {
  25. if (self = [super initWithFrame:frame]) {
  26. [self initialization];
  27. }
  28. return self;
  29. }
  30. - (void)awakeFromNib {
  31. [super awakeFromNib];
  32. [self initialization];
  33. //开始展示
  34. self.prepareToShow = YES;
  35. }
  36. //初始化
  37. - (instancetype)initWithFrame:(CGRect)frame
  38. pathBackColor:(UIColor *)pathBackColor
  39. pathFillColor:(UIColor *)pathFillColor
  40. startAngle:(CGFloat)startAngle
  41. strokeWidth:(CGFloat)strokeWidth {
  42. if (self = [super initWithFrame:frame]) {
  43. [self initialization];
  44. if (pathBackColor) {
  45. _pathBackColor = pathBackColor;
  46. }
  47. if (pathFillColor) {
  48. _pathFillColor = pathFillColor;
  49. }
  50. _startAngle = ZZCircleDegreeToRadian(startAngle);
  51. _strokeWidth = strokeWidth;
  52. }
  53. return self;
  54. }
  55. //初始化数据
  56. - (void)initialization {
  57. self.backgroundColor = [UIColor clearColor];
  58. _pathBackColor = [UIColor lightGrayColor];
  59. _pathFillColor = [UIColor redColor];
  60. _strokeWidth = 10;//线宽默认为10
  61. _startAngle = ZZCircleDegreeToRadian(0);//圆起点位置
  62. _reduceAngle = ZZCircleDegreeToRadian(0);//整个圆缺少的角度
  63. _duration = 1.5;//动画时长
  64. _showPoint = YES;//小圆点
  65. _showProgressText = YES;//文字
  66. _realWidth = ZZCircleSelfWidth>ZZCircleSelfHeight?ZZCircleSelfHeight:ZZCircleSelfWidth;
  67. _radius = _realWidth/2.0 - _strokeWidth/2.0;
  68. }
  69. #pragma Get
  70. - (CAShapeLayer *)backLayer {
  71. if (!_backLayer) {
  72. _backLayer = [CAShapeLayer layer];
  73. _backLayer.frame = CGRectMake((ZZCircleSelfWidth-_realWidth)/2.0, (ZZCircleSelfHeight-_realWidth)/2.0, _realWidth, _realWidth);
  74. _backLayer.fillColor = [UIColor colorWithRed:(1)/255.0f green:(1)/255.0f blue:(1)/255.0f alpha:0.3].CGColor;//填充色
  75. // _backLayer.fillColor = [UIColor clearColor].CGColor;//填充色
  76. _backLayer.lineWidth = _strokeWidth;
  77. _backLayer.strokeColor = _pathBackColor.CGColor;
  78. _backLayer.lineCap = @"round";
  79. UIBezierPath *backCirclePath = [self getNewBezierPath];
  80. _backLayer.path = backCirclePath.CGPath;
  81. }
  82. return _backLayer;
  83. }
  84. - (CAShapeLayer *)progressLayer {
  85. if (!_progressLayer) {
  86. _progressLayer = [CAShapeLayer layer];
  87. _progressLayer.frame = CGRectMake((ZZCircleSelfWidth-_realWidth)/2.0, (ZZCircleSelfHeight-_realWidth)/2.0, _realWidth, _realWidth);
  88. _progressLayer.fillColor = [UIColor clearColor].CGColor;//填充色
  89. _progressLayer.lineWidth = _strokeWidth;
  90. _progressLayer.strokeColor = _pathFillColor.CGColor;
  91. _progressLayer.lineCap = @"round";
  92. UIBezierPath *circlePath = [self getNewBezierPath];
  93. _progressLayer.path = circlePath.CGPath;
  94. _progressLayer.strokeEnd = 0.0;
  95. }
  96. return _progressLayer;
  97. }
  98. - (UIImageView *)pointImage {
  99. if (!_pointImage) {
  100. _pointImage = [[UIImageView alloc] init];
  101. NSBundle *mainBundle = [NSBundle bundleForClass:[self class]];
  102. NSBundle *resourcesBundle = [NSBundle bundleWithPath:[mainBundle pathForResource:@"ZZCircleProgress" ofType:@"bundle"]];
  103. _pointImage.image = [UIImage imageNamed:@"circle_point1" inBundle:resourcesBundle compatibleWithTraitCollection:nil];
  104. _pointImage.frame = CGRectMake(0, 0, _strokeWidth, _strokeWidth);
  105. //定位起点位置
  106. [self updatePointPosition];
  107. }
  108. return _pointImage;
  109. }
  110. - (ZZCountingLabel *)progressLabel {
  111. if (!_progressLabel) {
  112. _progressLabel = [[ZZCountingLabel alloc] init];
  113. _progressLabel.textColor = [UIColor blackColor];
  114. _progressLabel.textAlignment = NSTextAlignmentCenter;
  115. _progressLabel.font = [UIFont systemFontOfSize:22];
  116. _progressLabel.text = @"0%";
  117. _progressLabel.frame = CGRectMake(0, 0, ZZCircleSelfWidth, ZZCircleSelfHeight);
  118. }
  119. return _progressLabel;
  120. }
  121. -(UIImageView *)coinImg{
  122. if (!_coinImg) {
  123. _coinImg = [[UIImageView alloc]init];
  124. _coinImg.frame = CGRectMake(ZZCircleSelfWidth/4, ZZCircleSelfHeight/4, ZZCircleSelfWidth/2, ZZCircleSelfHeight/2);
  125. _coinImg.image = [UIImage imageNamed:@"zzcircle红包"];
  126. _coinImg.contentMode = UIViewContentModeScaleAspectFit;
  127. }
  128. return _coinImg;
  129. }
  130. #pragma Set
  131. - (void)setStartAngle:(CGFloat)startAngle {
  132. if (_startAngle != ZZCircleDegreeToRadian(startAngle)) {
  133. _startAngle = ZZCircleDegreeToRadian(startAngle);
  134. //如果已经创建了相关layer则重新创建
  135. if (_backLayer) {
  136. UIBezierPath *backCirclePath = [self getNewBezierPath];
  137. _backLayer.path = backCirclePath.CGPath;
  138. }
  139. if (_progressLayer) {
  140. UIBezierPath *circlePath = [self getNewBezierPath];
  141. _progressLayer.path = circlePath.CGPath;
  142. _progressLayer.strokeEnd = 0.0;
  143. }
  144. if (_pointImage) {
  145. //更新圆点位置
  146. [self updatePointPosition];
  147. }
  148. }
  149. }
  150. - (void)setReduceAngle:(CGFloat)reduceAngle {
  151. if (_reduceAngle != ZZCircleDegreeToRadian(reduceAngle)) {
  152. if (reduceAngle>=360) {
  153. return;
  154. }
  155. _reduceAngle = ZZCircleDegreeToRadian(reduceAngle);
  156. if (_backLayer) {
  157. UIBezierPath *backCirclePath = [self getNewBezierPath];
  158. _backLayer.path = backCirclePath.CGPath;
  159. }
  160. if (_progressLayer) {
  161. UIBezierPath *circlePath = [self getNewBezierPath];
  162. _progressLayer.path = circlePath.CGPath;
  163. _progressLayer.strokeEnd = 0.0;
  164. }
  165. if (_pointImage) {
  166. //更新圆点位置
  167. [self updatePointPosition];
  168. }
  169. }
  170. }
  171. - (void)setStrokeWidth:(CGFloat)strokeWidth {
  172. if (_strokeWidth != strokeWidth) {
  173. _strokeWidth = strokeWidth;
  174. _radius = _realWidth/2.0 - _strokeWidth/2.0;
  175. //设置线宽之后会导致radius改变,因此需要修改使用过strokeWidth和radius的属性
  176. if (_backLayer) {
  177. _backLayer.lineWidth = _strokeWidth;
  178. UIBezierPath *backCirclePath = [self getNewBezierPath];
  179. _backLayer.path = backCirclePath.CGPath;
  180. }
  181. if (_progressLayer) {
  182. _progressLayer.lineWidth = _strokeWidth;
  183. UIBezierPath *circlePath = [self getNewBezierPath];
  184. _progressLayer.path = circlePath.CGPath;
  185. _progressLayer.strokeEnd = 0.0;
  186. }
  187. if (_pointImage) {
  188. _pointImage.frame = CGRectMake(0, 0, _strokeWidth, _strokeWidth);
  189. //更新圆点位置
  190. [self updatePointPosition];
  191. }
  192. }
  193. }
  194. - (void)setPathBackColor:(UIColor *)pathBackColor {
  195. if (_pathBackColor != pathBackColor) {
  196. _pathBackColor = pathBackColor;
  197. if (_backLayer) {
  198. _backLayer.strokeColor = _pathBackColor.CGColor;
  199. }
  200. }
  201. }
  202. - (void)setPathFillColor:(UIColor *)pathFillColor {
  203. if (_pathFillColor != pathFillColor) {
  204. _pathFillColor = pathFillColor;
  205. if (_progressLayer) {
  206. _progressLayer.strokeColor = _pathFillColor.CGColor;
  207. }
  208. }
  209. }
  210. - (void)setShowPoint:(BOOL)showPoint {
  211. if (_showPoint != showPoint) {
  212. _showPoint = showPoint;
  213. if (_showPoint) {
  214. self.pointImage.hidden = NO;
  215. [self updatePointPosition];
  216. } else {
  217. self.pointImage.hidden = YES;
  218. }
  219. }
  220. }
  221. -(void)setShowProgressText:(BOOL)showProgressText {
  222. if (_showProgressText != showProgressText) {
  223. _showProgressText = showProgressText;
  224. if (_showProgressText) {
  225. self.progressLabel.hidden = NO;
  226. } else {
  227. self.progressLabel.hidden = YES;
  228. }
  229. }
  230. }
  231. - (void)setPrepareToShow:(BOOL)prepareToShow {
  232. if (_prepareToShow != prepareToShow) {
  233. _prepareToShow = prepareToShow;
  234. if (_prepareToShow) {
  235. [self initSubviews];
  236. }
  237. }
  238. }
  239. - (void)setProgress:(CGFloat)progress {
  240. //准备好显示
  241. self.prepareToShow = YES;
  242. _progress = progress;
  243. if (_progress < 0) {
  244. _progress = 0;
  245. }
  246. if (_progress > 1) {
  247. _progress = 1;
  248. }
  249. [self startAnimation];
  250. }
  251. - (void)startAnimation {
  252. //线条动画
  253. CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
  254. pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
  255. pathAnimation.duration = _duration;
  256. pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
  257. pathAnimation.fromValue = [NSNumber numberWithFloat:_increaseFromLast==YES?_lastProgress:0];
  258. pathAnimation.toValue = [NSNumber numberWithFloat:_progress];
  259. pathAnimation.fillMode = kCAFillModeForwards;
  260. pathAnimation.removedOnCompletion = NO;
  261. [self.progressLayer addAnimation:pathAnimation forKey:@"strokeEndAnimation"];
  262. if (_showPoint) {
  263. //小图片动画
  264. CAKeyframeAnimation *pointAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
  265. pointAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
  266. pointAnimation.fillMode = kCAFillModeForwards;
  267. pointAnimation.calculationMode = @"paced";
  268. pointAnimation.removedOnCompletion = NO;
  269. pointAnimation.duration = _duration;
  270. pointAnimation.delegate = self;
  271. BOOL clockwise = NO;
  272. if (_progress<_lastProgress && _increaseFromLast == YES) {
  273. clockwise = YES;
  274. }
  275. UIBezierPath *imagePath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(_realWidth/2.0, _realWidth/2.0) radius:_radius startAngle:_increaseFromLast==YES?(2*M_PI-_reduceAngle)*_lastProgress+_startAngle:_startAngle endAngle:(2*M_PI-_reduceAngle)*_progress+_startAngle clockwise:!clockwise];
  276. pointAnimation.path = imagePath.CGPath;
  277. [self.pointImage.layer addAnimation:pointAnimation forKey:nil];
  278. self.lastPointAnimation = pointAnimation;
  279. if (!_increaseFromLast && _progress == 0.0) {
  280. [self.pointImage.layer removeAllAnimations];
  281. }
  282. }
  283. if (_showProgressText) {
  284. if (_increaseFromLast) {
  285. [self.progressLabel countingFrom:_lastProgress*100 to:_progress*100 duration:_duration];
  286. } else {
  287. [self.progressLabel countingFrom:0 to:_progress*100 duration:_duration];
  288. }
  289. }
  290. _lastProgress = _progress;
  291. }
  292. //刷新最新路径
  293. - (UIBezierPath *)getNewBezierPath {
  294. return [UIBezierPath bezierPathWithArcCenter:CGPointMake(_realWidth/2.0, _realWidth/2.0) radius:_radius startAngle:_startAngle endAngle:(2*M_PI-_reduceAngle+_startAngle) clockwise:YES];
  295. }
  296. //监听动画结束
  297. - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
  298. if (flag && anim == _lastPointAnimation) {
  299. [self updatePointPosition];
  300. }
  301. }
  302. //更新小圆点的真实位置
  303. - (void)updatePointPosition {
  304. //重定位起点
  305. CGFloat currentEndAngle = (2*M_PI-_reduceAngle)*_progress+_startAngle;
  306. [_pointImage.layer removeAllAnimations];
  307. _pointImage.center = CGPointMake(_realWidth/2.0+_radius*cosf(currentEndAngle), _realWidth/2.0+_radius*sinf(currentEndAngle));
  308. }
  309. - (void)initSubviews {
  310. [self.layer addSublayer:self.backLayer];
  311. [self.layer addSublayer:self.progressLayer];
  312. [self addSubview:self.pointImage];
  313. [self addSubview:self.progressLabel];
  314. [self addSubview:self.coinImg];
  315. }
  316. @end