原文地址:http://zeeyang.com/2016/07/27/loadingAnimation-0727/
好久没写动画了...最近扒了下以前没有写的动画效果,想想从最老的开始写吧,之前看到的版本是用Swift写的,没仔细找有没有OC版的,所以干脆自己练习一下吧,我们先来看看效果:
(这里三角形是旋转动画,但是Gif录出来看上去是抖了两下...)
可以直接run下代码,看下效果:https://github.com/Yuzeyang/GCLoadingAnimation/tree/master/GCLoadingAnimationOne
下面我来分析下过程
这个动画的实现只用到了UIBezierPath
、CABasicAnimation
和CALayer
从Gif里面可以看到这个动画分为以下几个步骤:
1.从无到圆
2.圆x轴方向拉伸和y轴方向拉伸
3.“长出”三角形的三个角
4.三角形旋转
5.画两条边框
6.水面上涨动画
7.中间矩形放大至全屏
8.中间logo跟着出现
0x00 从无到圆
这个比较简单,只要设定起始的size为0和设定默认圆半径大小,用+ bezierPathWithOvalInRect:
方法画圆
+ (instancetype)bezierPathWithRoundedRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius
UIBezierPath *startPath = [self circleStartPath];
UIBezierPath *endPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(GCLoadingLayerCenterX - GCCircleRadius, GCLoadingLayerCenterY - GCCircleRadius, GCCircleRadius*2, GCCircleRadius*2)];
将最后圆的path
设为circleLayer
的path
self.circleLayer = [CAShapeLayer layer];
self.circleLayer.path = endPath.CGPath;
self.circleLayer.fillColor = [UIColor orangeColor].CGColor;
[self addSublayer:self.circleLayer];
然后加上动画,因为我们修改的是path
,所以我们animation
的keyPath
是path
(后面也是),设定起始值为startPath.CGPath
CABasicAnimation *circleAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
circleAnimation.fromValue = (__bridge id _Nullable)(startPath.CGPath);
circleAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
circleAnimation.duration = 0.2f;
circleAnimation.fillMode = kCAFillModeForwards;
circleAnimation.delegate = self;
circleAnimation.removedOnCompletion = NO;
[circleAnimation setValue:@"circleAnimation" forKey:@"animationName"];
[self.circleLayer addAnimation:circleAnimation forKey:nil];
0x01 圆x轴方向拉伸和y轴方向拉伸
这里我们的keyPath
不用transform.scale.x/y
,因为缩放之后,圆心会改变,看上去有偏移,这样动画写起来更复杂,所以我们干脆直接用拉伸后的path
来做动画
创建x轴、y轴拉伸后的path
,然后加到animation
里面
UIBezierPath *scaleXPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(GCLoadingLayerCenterX - GCCircleRadius*1.1, GCLoadingLayerCenterY - GCCircleRadius, GCCircleRadius*2.2, GCCircleRadius*2)];
UIBezierPath *scaleYPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(GCLoadingLayerCenterX - GCCircleRadius, GCLoadingLayerCenterY - GCCircleRadius*1.1, GCCircleRadius*2, GCCircleRadius*2.2)];
CABasicAnimation *circleScaleXOneAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
circleScaleXOneAnimation.fromValue = (__bridge id _Nullable)(self.circleLayer.path);
circleScaleXOneAnimation.toValue = (__bridge id _Nullable)(scaleXPath.CGPath);
circleScaleXOneAnimation.duration = 0.2f;
circleScaleXOneAnimation.beginTime = 0.0;
```
一共四个`CABasicAnimation`对象,然后我们将这些动画加到`CAAnimationGroup`里
CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
animationGroup.animations = @[circleScaleXOneAnimation,circleScaleXTwoAnimation,circleScaleYOneAnimation,circleScaleYTwoAnimation];
animationGroup.duration = circleScaleYTwoAnimation.beginTime + circleScaleYTwoAnimation.duration;
animationGroup.delegate = self;
[animationGroup setValue:@"circleScaleAnimation" forKey:@"animationName"];
[self.circleLayer addAnimation:animationGroup forKey:nil];
##0x02 “长出”三角形的三个角
实际上三角形在等到圆形出现或者圆形拉伸完之后就已经在那了,“长出角”的感觉实际上只是改变了绘制的三个点的位置,首先我们根据圆的半径画出三角形
UIBezierPath *originTrianglePath = [UIBezierPath bezierPath];
[originTrianglePath moveToPoint:[self triangleLeftPointWithScale:1.0]];
[originTrianglePath addLineToPoint:[self triangleRightPointWithScale:1.0]];
[originTrianglePath addLineToPoint:[self triangleTopPointWithScale:1.0]];
[originTrianglePath closePath];
self.triangleLayer = [CAShapeLayer layer];
self.triangleLayer.path = originTrianglePath.CGPath;
self.triangleLayer.fillColor = [UIColor orangeColor].CGColor;
[self addSublayer:self.triangleLayer];
然后改变左边点的位置
UIBezierPath *blowUpLeftTrianglePath = [UIBezierPath bezierPath];
[blowUpLeftTrianglePath moveToPoint:[self triangleLeftPointWithScale:1.2]];
[blowUpLeftTrianglePath addLineToPoint:[self triangleRightPointWithScale:1.0]];
[blowUpLeftTrianglePath addLineToPoint:[self triangleTopPointWithScale:1.0]];
[blowUpLeftTrianglePath closePath];
也加上`path`的动画
CABasicAnimation *blowUpLeftAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
blowUpLeftAnimation.fromValue = (__bridge id _Nullable)(self.triangleLayer.path);
blowUpLeftAnimation.toValue = (__bridge id _Nullable)(blowUpLeftTrianglePath.CGPath);
blowUpLeftAnimation.duration = 0.2f;
blowUpLeftAnimation.beginTime = 0.0;
右边和上边的点同理,然后也一起加到`CAAnimationGroup`里
##0x03 三角形旋转
旋转就比较简单了,只要根据z轴旋转设定的角度即可
CABasicAnimation rotationAniamtion = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
rotationAniamtion.toValue = @(M_PI2);
rotationAniamtion.duration = 0.4;
rotationAniamtion.fillMode = kCAFillModeForwards;
rotationAniamtion.delegate = self;
rotationAniamtion.beginTime = CACurrentMediaTime();
[rotationAniamtion setValue:@"rotationAniamtion" forKey:@"animationName"];
[self.triangleLayer addAnimation:rotationAniamtion forKey:nil];
##0x04 画两条边框
这两个边框绘制方法是一模一样的,只是中间有个时间间隔而已
-
(CABasicAnimation *)drawRectWithLineColor:(CGColorRef)color animationValue:(NSString *)animationValue {
CGPoint startPoint = [self triangleLeftPointWithScale:1.2];
UIBezierPath *rectPath = [UIBezierPath bezierPath];[rectPath moveToPoint:startPoint];
[rectPath addLineToPoint:CGPointMake(startPoint.x, startPoint.y - GCCircleRadius2.4)];
[rectPath addLineToPoint:CGPointMake(startPoint.x + powf(3, 0.5)GCCircleRadius1.2, startPoint.y - GCCircleRadius2.4)];[rectPath addLineToPoint:CGPointMake(startPoint.x + powf(3, 0.5)GCCircleRadius1.2, startPoint.y - 2)];
[rectPath addLineToPoint:CGPointMake(startPoint.x - 2.5, startPoint.y - 2)];
CAShapeLayer *layer = [CAShapeLayer layer];
layer.path = rectPath.CGPath;
layer.lineWidth = 5;
layer.strokeColor = color;
layer.fillColor = [UIColor clearColor].CGColor;
[self addSublayer:layer];
CABasicAnimation *rectAniamtion = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
rectAniamtion.fromValue = @(0.0);
rectAniamtion.toValue = @(1.0);
rectAniamtion.duration = 0.8;
rectAniamtion.delegate = self;
if (animationValue.length) {
[rectAniamtion setValue:@"rectAniamtion" forKey:@"animationName"];
}
[layer addAnimation:rectAniamtion forKey:nil];
return rectAniamtion;
}
间隔的话,我们直接调用`- performSelector: withObject: afterDelay:`来延迟执行第二条边框的绘制就好
##0x05 水面上涨动画
这个动画的关键就是用`- addCurveToPoint: controlPoint1: controlPoint2:`方法来画出水波的线,这个方法主要是利用`controlPoint1`和`controlPoint2`这两个点来控制弧度方向,如图:
![](http://upload-images.jianshu.io/upload_images/744236-1f22283e0d7fe7de.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
然后我们只需要交叉改变 `controlPoint1`和`controlPoint2`这两个点在上下的位置和`startPoint`和`endPoint`的位置,就能感觉水面上涨的感觉
NSMutableArray <UIBezierPath *> *waterPathArray = [NSMutableArray array];
for (NSInteger i = 0; i < 11; i++) {
UIBezierPath water = [self water:i % 2 == 0 ? YES : NO withProgress:0.1i];
[waterPathArray addObject:water];
}
创建完毕`path`之后,将`anmations`放到`CAAnimationGroup`
里面
- (void)addWaterAnimation:(NSMutableArray <UIBezierPath *> *)waterArray {
NSMutableArray <CABasicAnimation *> *animationArray = [NSMutableArray array];
for (NSInteger i = 0; i < waterArray.count - 1; i++) {
CABasicAnimation *waterAniamtion = [CABasicAnimation animationWithKeyPath:@"path"];
waterAniamtion.fromValue = (__bridge id _Nullable)(waterArray[i].CGPath);
waterAniamtion.toValue = (__bridge id _Nullable)(waterArray[i + 1].CGPath);
waterAniamtion.duration = 0.2;
waterAniamtion.beginTime = i == 0 ? 0.0 : animationArray[i - 1].beginTime + animationArray[i - 1].duration;
[animationArray addObject:waterAniamtion];
}
CAAnimationGroup *group = [CAAnimationGroup animation];
group.animations = animationArray;
group.duration = [animationArray lastObject].beginTime + [animationArray lastObject].duration;
group.fillMode = kCAFillModeForwards;
group.removedOnCompletion = NO;
group.delegate = self;
[group setValue:@"waterAnimation" forKey:@"animationName"];
[self.waterLayer addAnimation:group forKey:nil];
}
##0x06 中间矩形放大至全屏
和前面一样,创建好全屏大小的`path`之后,然后加上动画即可
##0x07 中间logo跟着出现
这个改变`bounds`即可
CALayer *logoLayer = [CALayer layer];
logoLayer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"logo.jpg"].CGImage);
logoLayer.frame = CGRectMake(GCLoadingLayerCenterX, GCLoadingLayerCenterY, 0, 0);
[self addSublayer:logoLayer];
CABasicAnimation *logoAnimation = [CABasicAnimation animationWithKeyPath:@"bounds"];
logoAnimation.toValue = [NSValue valueWithCGRect:CGRectMake(GCLoadingLayerCenterX, GCLoadingLayerCenterY, 100, 120)];
logoAnimation.duration = 0.2;
logoAnimation.beginTime = 0.0;
logoAnimation.removedOnCompletion = NO;
logoAnimation.fillMode = kCAFillModeForwards;
[logoLayer addAnimation:logoAnimation forKey:nil];
这个加载动画的缺点就是在加载时没有可定制化的�形状,只能修改圆形等的颜色,如果要改变形状,可能会涉及到动效的改动,所以这个动画只能作为学习分析参考