由于项目需求,最近做了一个水滴落下的加载动画。最简单的实现当然是用UIImageView直接播放,但是考虑到动画的帧数太多,会导致整个应用程序包增大,还有水滴落下和水面碰撞的效果不够随机,最后直接用纯代码实现。
波纹的实现
波浪动画用CAShapeLayer和正弦曲线来模拟,刷新频率为屏幕刷新频率的一半,即30帧每秒。屏幕的刷新用到CADisplayLink,CADisplayLink的精确度优于NSTimer,非常适合这种和屏幕刷新相关的操作。
加载波形层
- (void)loadWaveLayers{
if (!_waveLayers)
{
self.waveLayers = [[NSMutableArray alloc] initWithCapacity:3];
UIColor *whiteColor = [UIColor whiteColor];
UIColor *lightGrayColor = [UIColor colorWithRed:207/255.0f
green:207/255.0f
blue:207/255.0f
alpha:1.0f];
UIColor *darkGrayColor = [UIColor colorWithRed:159/255.0f
green:159/255.0f
blue:159/255.0f
alpha:1.0f];
NSArray *colors = @[darkGrayColor, lightGrayColor, whiteColor];
for (int i = 0; i < 3; ++i) {
CAShapeLayer *waveLayer = [CAShapeLayer layer];
waveLayer.fillColor = ((UIColor *)colors[i]).CGColor;
[self.layer addSublayer:waveLayer];
[_waveLayers addObject:waveLayer];
}
}
}
设置定时器
_frameTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateVibrations:)];
_frameTimer.frameInterval = 2;
[_frameTimer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
更新数值
- (void)updateVibrations:(CADisplayLink *)displayLink
{
if (_increase) {
_variable += 1 / 30.0f;
}else{
_variable -= 1 / 30.0f;
}
if (_variable <= 1) {
_increase = YES;
}
if (_variable >= 1.6) {
_increase = NO;
}
_offsetX += _waveSpeed;
[self drawWaveWithLayer:_waveLayers[0] amplitude:_variable * 3 waveCycle:1.5* M_PI / _waveWidth offsetY:20.0f]; // dark
[self drawWaveWithLayer:_waveLayers[1] amplitude:_variable * 5 waveCycle:1.1 * M_PI / _waveWidth offsetY:22.0f]; // light
[self drawWaveWithLayer:_waveLayers[2] amplitude:_variable * 10 waveCycle:1 * M_PI / _waveWidth offsetY:22.0f]; // white
}
绘制正弦曲线
- (void)drawWaveWithLayer:(CAShapeLayer *)waveLayer
amplitude:(CGFloat)amplitude
waveCycle:(CGFloat)waveCycle
offsetY:(CGFloat)offsetY
{
CGMutablePathRef path = CGPathCreateMutable();
CGFloat y = self.bounds.size.height * 0.5f;
CGPathMoveToPoint(path, nil, 0, y);
for (float x = 0.0f; x <= _waveWidth ; x++) {
y = amplitude * sin(waveCycle * x + _offsetX) + self.frame.size.height * 0.5f + offsetY; // 正弦波
CGPathAddLineToPoint(path, nil, x, y);
}
CGPathAddLineToPoint(path, nil, _waveWidth, self.frame.size.height);
CGPathAddLineToPoint(path, nil, 0, self.frame.size.height);
CGPathCloseSubpath(path);
waveLayer.path = path;
CGPathRelease(path);
}
绘制水滴
绘制水滴的视图
自定义一个View继承自UIView,在drawRect中实现:
- (void)drawRect:(CGRect)rect {
// Drawing code
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextMoveToPoint(context, self.bounds.size.width * 0.5f, 0);
[[UIColor whiteColor] set];
CGContextSetLineWidth(context, 1.0);
CGContextAddCurveToPoint(context,
0,
self.bounds.size.height,
self.bounds.size.width,
self.bounds.size.height,
self.bounds.size.width * 0.5f,
0);
CGContextSetFillColorWithColor(context,[UIColor whiteColor].CGColor);
CGContextFillPath(context);
CGContextStrokePath(context);
}
这里要保证水滴视图的宽高比为2:1,这样贝塞尔曲线绘制出来的才是水滴形状。
给水滴添加重力和碰撞检测
这里用到了UIKit动力学。
UIGravityBehavior *gravityBeahvior = [[UIGravityBehavior alloc] initWithItems:@[_dripView]];
[_animator addBehavior:gravityBeahvior];
UICollisionBehavior *collisionBehavior = [[UICollisionBehavior alloc] initWithItems:@[_dripView]];
collisionBehavior.translatesReferenceBoundsIntoBoundary = NO;
CGPoint rightEdge = CGPointMake(_barrierLine.frame.origin.x + _barrierLine.frame.size.width,
_barrierLine.frame.origin.y);
[collisionBehavior addBoundaryWithIdentifier:@"barrier"
fromPoint:_barrierLine.frame.origin
toPoint:rightEdge];
collisionBehavior.collisionDelegate = (id)self;
[_animator addBehavior:collisionBehavior];
UIDynamicItemBehavior *itemBehaviour = [[UIDynamicItemBehavior alloc] initWithItems:@[_dripView, _barrierLine]];
itemBehaviour.elasticity = 0.0;
[_animator addBehavior:itemBehaviour];
水滴和波浪碰撞后的喷溅效果
创建圆形水珠
- (void)splashWater
{
if (!_dripLayers) {
_dripLayers = [[NSMutableArray alloc] initWithCapacity:kDripCount];
for (int i = 0; i < kDripCount; i++) {
CALayer *dripLayer = [CALayer layer];
[_dripLayers addObject:dripLayer];
}
}
for (int i = 0; i < kDripCount; i++) {
[self performSelector:@selector(addAnimationToDrip:) withObject:_dripLayers[i] afterDelay:i * 0.01];
}
}
给水珠添加抛物线动画
- (void)addAnimationToDrip:(CALayer *)dripLayer
{
CGFloat width = arc4random() % 15 + 1;
dripLayer.frame = CGRectMake((self.bounds.size.width - width)* 0.5f, self.bounds.size.height * 0.5f + 40, width, width);
dripLayer.cornerRadius = dripLayer.frame.size.width * 0.5f;
dripLayer.backgroundColor = [UIColor whiteColor].CGColor;
[self.layer addSublayer:dripLayer];
CGFloat x3 = arc4random() % ((int)self.bounds.size.width) + 1;
CGFloat y3 = self.bounds.size.height * 0.5f + 40;
CGFloat height = arc4random() % ((int)(self.bounds.size.height * 0.5f));
[self throwDrip:dripLayer
from:dripLayer.position
to:CGPointMake(x3, y3)
height:height
duration:0.7f];
}
- (void)throwDrip:(CALayer *)drip
from:(CGPoint)start
to:(CGPoint)end
height:(CGFloat)height
duration:(CGFloat)duration
{
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, start.x, start.y);
CGPathAddQuadCurveToPoint(path, NULL, (end.x + start.x) * 0.5f, -height, end.x, end.y);
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
[animation setPath:path];
animation.duration = duration;
CFRelease(path);
path = nil;Demotic
[drip addAnimation:animation forKey:@"position"];
}