CircleView
#import <UIKit/UIKit.h>
typedef void(^ScaleCompletionHandler)();
@interface CircleView : UIView
- (instancetype) initWithFrame:(CGRect)frame andTheMaxSize:(CGFloat)maxSize theMinSize:(CGFloat)minSize theColor:(UIColor *)color;
- (void) startHeartBeat;
- (void) stopHeartBeat;
@end
#import "CircleView.h"
#pragma mark - CircleLayer class
@interface CircleLayer : CAShapeLayer
- (instancetype)initWithSuperViewFrame:(CGRect)superLayerFrame theMaxSize:(CGFloat)maxSize theMinSize:(CGFloat)minSize andTheColor:(UIColor *)color;
- (void) startAnimation;
- (void) stopAnimation;
@end
@interface CircleLayer ()
@property (nonatomic, assign) CGFloat maxSize;
@property (nonatomic, assign) CGFloat minSize;
@end
@implementation CircleLayer
- (instancetype)initWithSuperViewFrame:(CGRect)superLayerFrame theMaxSize:(CGFloat)maxSize theMinSize:(CGFloat)minSize andTheColor:(UIColor *)color {
self = [super init];
if (self) {
self.maxSize = maxSize;
self.minSize = minSize;
self.frame = CGRectMake(superLayerFrame.size.width/2 - maxSize/2, superLayerFrame.size.height/2 - maxSize/2, maxSize, maxSize);
UIBezierPath *circlePath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, maxSize, maxSize)];
self.path = circlePath.CGPath;
self.fillColor = color.CGColor;
self.lineWidth = 0;
}
return self;
}
#pragma mark Public methods
- (void)startAnimation {
[self performScaleAnimation];
}
- (void)stopAnimation {
[self removeAllAnimations];
}
#pragma mark Private methods
- (void) performScaleAnimation {
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"];
animation.keyTimes = @[@0, @0.5, @1];
animation.values = @[@(self.maxSize / self.maxSize), @(self.minSize / self.maxSize), @(self.maxSize / self.maxSize)];
animation.timingFunctions = @[[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault], [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
animation.repeatCount = HUGE;
animation.duration = 1.0;
[self addAnimation:animation forKey:animation.keyPath];
}
@end
#pragma mark - WireLayer class
@interface WireLayer : CAShapeLayer
- (instancetype) initWithSuperLayerFrame:(CGRect)superLayerFrame
theMaxSize:(CGFloat)maxSize
theMinSize:(CGFloat)minSize theStartAngle:(CGFloat)startAngle theEndAngle:(CGFloat)endAngle
andTheColor:(UIColor *)color;
- (void) startAnimationWithStartAngle:(CGFloat)startAngle andEndAngle:(CGFloat)endAngle;
- (void) stopAnimation;
@end
@interface WireLayer ()
@property (nonatomic, assign) CGFloat maxSize;
@property (nonatomic, assign) CGFloat minSize;
@end
@implementation WireLayer
#pragma mark Override
- (instancetype)initWithSuperLayerFrame:(CGRect)superLayerFrame
theMaxSize:(CGFloat)maxSize
theMinSize:(CGFloat)minSize theStartAngle:(CGFloat)startAngle theEndAngle:(CGFloat)endAngle
andTheColor:(UIColor *)color {
self = [super init];
if (self) {
self.maxSize = maxSize * 1.6;
self.minSize = minSize * 2.3;
self.frame = CGRectMake(superLayerFrame.size.width/2 - self.maxSize/2, superLayerFrame.size.height/2 - self.maxSize/2, self.maxSize, self.maxSize);
UIBezierPath *wire = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2) radius:self.maxSize / 2 startAngle:startAngle endAngle:endAngle clockwise:YES];
self.path = wire.CGPath;
self.fillColor = nil;
self.strokeColor = color.CGColor;
self.lineWidth = 2.5;
self.backgroundColor = [UIColor clearColor].CGColor;
}
return self;
}
#pragma mark Public methods
- (void)startAnimationWithStartAngle:(CGFloat)startAngle andEndAngle:(CGFloat)endAngle {
[self performScaleAndRotationAnimationWithStartAngle:startAngle EndAngle:endAngle];
}
- (void)stopAnimation {
[self removeAllAnimations];
}
#pragma mark Private methods
- (void) performScaleAndRotationAnimationWithStartAngle:(CGFloat)startAngle EndAngle:(CGFloat)endAngle {
CAKeyframeAnimation *scaleAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"];
scaleAnimation.keyTimes = @[@0, @0.5, @1];
scaleAnimation.values = @[@(self.maxSize / self.maxSize), @(self.minSize / self.maxSize), @(self.maxSize / self.maxSize)];
scaleAnimation.timingFunctions = @[[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault], [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]];
CAKeyframeAnimation *rotationAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation.z"];
rotationAnimation.keyTimes = @[@0, @0.5, @1];
rotationAnimation.values = @[@(startAngle), @(endAngle), @(startAngle + 2*M_PI)];
rotationAnimation.timingFunctions = @[[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault], [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]];
CAAnimationGroup *group = [CAAnimationGroup animation];
group.animations = @[rotationAnimation, scaleAnimation];
group.duration = 1.0;
group.repeatCount = HUGE;
[self addAnimation:group forKey:@"group"];
}
@end
#pragma mark - CircleView @implementation
@interface CircleView ()
@property (nonatomic, strong) CircleLayer *circleLayer;
@property (nonatomic, strong) WireLayer *topWireLayer;
@property (nonatomic, strong) WireLayer *bottomWireLayer;
@end
@implementation CircleView
#pragma mark Override
- (instancetype)initWithFrame:(CGRect)frame andTheMaxSize:(CGFloat)maxSize theMinSize:(CGFloat)minSize theColor:(UIColor *)color {
self = [super initWithFrame:frame];
if (self) {
self.circleLayer = [[CircleLayer alloc] initWithSuperViewFrame:self.bounds theMaxSize:maxSize theMinSize:minSize andTheColor:color];
[self.layer addSublayer:self.circleLayer];
self.topWireLayer = [[WireLayer alloc] initWithSuperLayerFrame:self.bounds theMaxSize:maxSize theMinSize:minSize theStartAngle:(M_PI + M_PI_4) theEndAngle:(2*M_PI - M_PI_4) andTheColor:color];
[self.layer addSublayer:self.topWireLayer];
self.bottomWireLayer = [[WireLayer alloc] initWithSuperLayerFrame:self.bounds theMaxSize:maxSize theMinSize:minSize theStartAngle:(M_PI_4) theEndAngle:(M_PI_2 + M_PI_4) andTheColor:color];
[self.layer addSublayer:self.bottomWireLayer];
}
return self;
}
#pragma mark Public methods
- (void)startHeartBeat {
[self.circleLayer startAnimation];
[self.topWireLayer startAnimationWithStartAngle:(0) andEndAngle:(M_PI)];
[self.bottomWireLayer startAnimationWithStartAngle:(0) andEndAngle:(M_PI)]; // 以自己的角度坐标为标准
}
- (void)stopHeartBeat {
[self.circleLayer stopAnimation];
[self.topWireLayer stopAnimation];
[self.bottomWireLayer stopAnimation];
}
@end
GrilleView
#import <UIKit/UIKit.h>
@interface GrilleView : UIView
- (instancetype) initWithFrame:(CGRect)frame theMaxSize:(CGFloat)maxSize theMinSize:(CGFloat)minSize andTheGrilleColor:(UIColor *)color;
- (void) startWave;
- (void) stopWave;
@end
#import "GrilleView.h"
#import "Utils.h"
#pragma mark - GrilleLayer class
@interface GrilleLayer : CAShapeLayer
- (instancetype) initWithFrame:(CGRect)frame maxSize:(CGFloat)maxSize minSize:(CGFloat)minSize andTheColor:(UIColor *)color;
- (void) makeExpansion;
- (void) stopExpansion;
@end
@interface GrilleLayer ()
@property (nonatomic, assign) CGFloat maxSize;
@property (nonatomic, assign) CGFloat minSize;
@end
@implementation GrilleLayer
#pragma mark Override
- (instancetype)initWithFrame:(CGRect)frame maxSize:(CGFloat)maxSize minSize:(CGFloat)minSize andTheColor:(UIColor *)color {
self = [super init];
if (self) {
self.maxSize = maxSize;
self.minSize = minSize;
self.frame = frame;
self.backgroundColor = [UIColor clearColor].CGColor;
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(frame.size.width/2, frame.size.height - (frame.size.height/2 - minSize/2))];
[path addLineToPoint:CGPointMake(frame.size.width/2, frame.size.height/2 - minSize/2)];
self.path = path.CGPath;
self.strokeColor = color.CGColor;
self.lineWidth = 6.0;
self.lineCap = kCALineCapSquare;
}
return self;
}
#pragma mark Public method
- (void)makeExpansion {
CAKeyframeAnimation *expansionAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale.y"];
expansionAnimation.timingFunctions = @[[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault], [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]];
expansionAnimation.keyTimes = @[@0, @0.45, @0.9];
expansionAnimation.values = @[@(self.minSize / self.minSize), @(self.maxSize / self.minSize), @(self.minSize / self.minSize)];
expansionAnimation.duration = 0.9;
expansionAnimation.repeatCount = HUGE;
[self addAnimation:expansionAnimation forKey:expansionAnimation.keyPath];
}
- (void)stopExpansion {
[self removeAllAnimations];
}
@end
#pragma mark -GrilleView @implementation
@interface GrilleView ()
@property (nonatomic, copy) NSArray *grilles;
@property (nonatomic, assign) NSTimeInterval timeInterval;
@end
@implementation GrilleView
#pragma mark Override
- (instancetype)initWithFrame:(CGRect)frame theMaxSize:(CGFloat)maxSize theMinSize:(CGFloat)minSize andTheGrilleColor:(UIColor *)color {
self = [super initWithFrame:frame];
if (self) {
self.timeInterval = 0.0f;
CGFloat margin = 6.0f;
GrilleLayer *grille_mid = [[GrilleLayer alloc] initWithFrame:CGRectMake(self.frame.size.width/2 - margin/2, self.frame.size.height/2 - maxSize/2, margin, maxSize) maxSize:maxSize minSize:minSize andTheColor:color];
GrilleLayer *grille_left_one = [[GrilleLayer alloc] initWithFrame:CGRectMake(grille_mid.frame.origin.x - 4*margin, grille_mid.frame.origin.y, margin, maxSize) maxSize:maxSize minSize:minSize andTheColor:color];
GrilleLayer *grille_left_two = [[GrilleLayer alloc] initWithFrame:CGRectMake(grille_mid.frame.origin.x - 2*margin, grille_mid.frame.origin.y, margin, maxSize) maxSize:maxSize minSize:minSize andTheColor:color];
GrilleLayer *grille_right_two = [[GrilleLayer alloc] initWithFrame:CGRectMake(grille_mid.frame.origin.x + 2*margin, grille_mid.frame.origin.y, margin, maxSize) maxSize:maxSize minSize:minSize andTheColor:color];
GrilleLayer *grille_right_one = [[GrilleLayer alloc] initWithFrame:CGRectMake(grille_mid.frame.origin.x + 4*margin, grille_mid.frame.origin.y, margin, maxSize) maxSize:maxSize minSize:minSize andTheColor:color];
self.grilles = @[grille_left_one, grille_left_two, grille_mid, grille_right_two, grille_right_one];
[self.grilles enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[self.layer addSublayer:(GrilleLayer *)obj];
}];
}
return self;
}
#pragma mark Public methods
- (void)startWave {
[self.grilles enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
GrilleLayer *grille = (GrilleLayer *)obj;
delay(self.timeInterval, ^{
[grille makeExpansion];
});
self.timeInterval += 0.15;
}];
}
- (void)stopWave {
[self.grilles enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
GrilleLayer *grille = (GrilleLayer *)obj;
[grille stopExpansion];
}];
}
@end