变更记录

序号 录入时间 录入人 备注
1 2015-03-18 Alfred Jiang -
2 2015-12-22 Alfred Jiang -

方案名称

特殊控件 - 使用 RecordingCircleOverlayView 实现环形记录仪动画

关键字

特殊控件 \ 动画 \ record \ 环形 \ 记录

需求场景

  1. 录音或者录像显示进度动画

参考链接

  1. Spark Camera’s recording meter
  2. GitHub - SparkRecordingCircle

详细内容

#####1. RecordingCircleOverlayView.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//
// RecordingCircleOverlayView.h
// SparkRecordingCircle
//
// Created by Sam Page on 1/02/14.
// Copyright (c) 2014 Sam Page. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface RecordingCircleOverlayView : UIView

- (id)initWithFrame:(CGRect)frame strokeWidth:(CGFloat)strokeWidth insets:(UIEdgeInsets)insets;

@property (nonatomic, assign) CGFloat duration;

@end

#####2. RecordingCircleOverlayView.m

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
//
// RecordingCircleOverlayView.m
// SparkRecordingCircle
//
// Created by Sam Page on 1/02/14.
// Copyright (c) 2014 Sam Page. All rights reserved.
//

#import "RecordingCircleOverlayView.h"

@interface RecordingCircleOverlayView ()

@property (nonatomic, strong) NSMutableArray *progressLayers;
@property (nonatomic, strong) UIBezierPath *circlePath;

@property (nonatomic, strong) CAShapeLayer *currentProgressLayer;
@property (nonatomic, strong) CAShapeLayer *backgroundLayer;

@property (nonatomic, assign) CGFloat strokeWidth;
@property (nonatomic, assign, getter = isCircleComplete) BOOL circleComplete;

@end

@implementation RecordingCircleOverlayView

- (id)initWithFrame:(CGRect)frame strokeWidth:(CGFloat)strokeWidth insets:(UIEdgeInsets)insets
{
if (self = [super initWithFrame:frame])
{
self.duration = 45.f;
self.strokeWidth = strokeWidth;
self.progressLayers = [NSMutableArray array];

CGPoint arcCenter = CGPointMake(CGRectGetMidY(self.bounds), CGRectGetMidX(self.bounds));
CGFloat radius = CGRectGetMidX(self.bounds) - insets.top - insets.bottom;

self.circlePath = [UIBezierPath bezierPathWithArcCenter:arcCenter
radius:radius
startAngle:M_PI
endAngle:-M_PI
clockwise:NO];
[self addBackgroundLayer];
}
return self;
}

- (void)addBackgroundLayer
{
self.backgroundLayer = [CAShapeLayer layer];
self.backgroundLayer.path = self.circlePath.CGPath;
self.backgroundLayer.strokeColor = [[UIColor lightGrayColor] CGColor];
self.backgroundLayer.fillColor = [[UIColor clearColor] CGColor];
self.backgroundLayer.lineWidth = self.strokeWidth;

[self.layer addSublayer:self.backgroundLayer];
}

- (void)addNewLayer
{
CAShapeLayer *progressLayer = [CAShapeLayer layer];
progressLayer.path = self.circlePath.CGPath;
progressLayer.strokeColor = [[self randomColor] CGColor];
progressLayer.fillColor = [[UIColor clearColor] CGColor];
progressLayer.lineWidth = self.strokeWidth;
progressLayer.strokeEnd = 0.f;

[self.layer addSublayer:progressLayer];
[self.progressLayers addObject:progressLayer];

self.currentProgressLayer = progressLayer;
}

- (UIColor *)randomColor
{
CGFloat hue = ( arc4random() % 256 / 256.0 ); // 0.0 to 1.0
CGFloat saturation = ( arc4random() % 128 / 256.0 ) + 0.5; // 0.5 to 1.0, away from white
CGFloat brightness = ( arc4random() % 128 / 256.0 ) + 0.5; // 0.5 to 1.0, away from black
return [UIColor colorWithHue:hue saturation:saturation brightness:brightness alpha:1.f];
}

- (void)updateAnimations
{
CGFloat duration = self.duration * (1.f - [[self.progressLayers firstObject] strokeEnd]);
CGFloat strokeEndFinal = 1.f;

for (CAShapeLayer *progressLayer in self.progressLayers)
{
CABasicAnimation *strokeEndAnimation = nil;
strokeEndAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
strokeEndAnimation.duration = duration;
strokeEndAnimation.fromValue = @(progressLayer.strokeEnd);
strokeEndAnimation.toValue = @(strokeEndFinal);
strokeEndAnimation.autoreverses = NO;
strokeEndAnimation.repeatCount = 0.f;

CGFloat previousStrokeEnd = progressLayer.strokeEnd;
progressLayer.strokeEnd = strokeEndFinal;

[progressLayer addAnimation:strokeEndAnimation forKey:@"strokeEndAnimation"];

strokeEndFinal -= (previousStrokeEnd - progressLayer.strokeStart);

if (progressLayer != self.currentProgressLayer)
{
CABasicAnimation *strokeStartAnimation = nil;
strokeStartAnimation = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
strokeStartAnimation.duration = duration;
strokeStartAnimation.fromValue = @(progressLayer.strokeStart);
strokeStartAnimation.toValue = @(strokeEndFinal);
strokeStartAnimation.autoreverses = NO;
strokeStartAnimation.repeatCount = 0.f;

progressLayer.strokeStart = strokeEndFinal;

[progressLayer addAnimation:strokeStartAnimation forKey:@"strokeStartAnimation"];
}
}

CABasicAnimation *backgroundLayerAnimation = nil;
backgroundLayerAnimation = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
backgroundLayerAnimation.duration = duration;
backgroundLayerAnimation.fromValue = @(self.backgroundLayer.strokeStart);
backgroundLayerAnimation.toValue = @(1.f);
backgroundLayerAnimation.autoreverses = NO;
backgroundLayerAnimation.repeatCount = 0.f;
backgroundLayerAnimation.delegate = self;

self.backgroundLayer.strokeStart = 1.0;

[self.backgroundLayer addAnimation:backgroundLayerAnimation forKey:@"strokeStartAnimation"];
}

- (void)updateLayerModelsForPresentationState
{
for (CAShapeLayer *progressLayer in self.progressLayers)
{
progressLayer.strokeStart = [progressLayer.presentationLayer strokeStart];
progressLayer.strokeEnd = [progressLayer.presentationLayer strokeEnd];
[progressLayer removeAllAnimations];
}

self.backgroundLayer.strokeStart = [self.backgroundLayer.presentationLayer strokeStart];
[self.backgroundLayer removeAllAnimations];
}

#pragma UIResponder overrides

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if (self.isCircleComplete == NO)
{
[self addNewLayer];
[self updateAnimations];
}
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
if (self.isCircleComplete == NO)
{
[self updateLayerModelsForPresentationState];
}
}

#pragma mark - CAAnimation Delegate

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
if (self.isCircleComplete == NO && flag)
{
self.circleComplete = flag;
}
}

@end

#####3. 使用

1
2
3
4
RecordingCircleOverlayView *recordingCircleOverlayView = [[RecordingCircleOverlayView alloc] initWithFrame:CGRectMake(0, 0, 200, 200) strokeWidth:7.f insets:UIEdgeInsetsMake(10.f, 0.f, 10.f, 0.f)];
recordingCircleOverlayView.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth;
recordingCircleOverlayView.duration = 10.f;
[self.view addSubview:recordingCircleOverlayView];

效果图

Image

备注

(无)