好久没写动画了…最近扒了下以前没有写的动画效果,想想从最老的开始写吧,之前看到的版本是用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
1 2
| UIBezierPath *startPath = [self circleStartPath]; UIBezierPath *endPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(GCLoadingLayerCenterX - GCCircleRadius, GCLoadingLayerCenterY - GCCircleRadius, GCCircleRadius*2, GCCircleRadius*2)];
|
将最后圆的path
设为circleLayer
的path
1 2 3 4
| 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
1 2 3 4 5 6 7 8 9
| 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
里面
1 2 3 4 5 6 7 8
| 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
里
1 2 3 4 5 6
| 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 “长出”三角形的三个角
实际上三角形在等到圆形出现或者圆形拉伸完之后就已经在那了,“长出角”的感觉实际上只是改变了绘制的三个点的位置,首先我们根据圆的半径画出三角形
1 2 3 4 5 6 7 8 9 10
| 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];
|
然后改变左边点的位置
1 2 3 4 5
| 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
的动画
1 2 3 4 5
| 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轴旋转设定的角度即可
1 2 3 4 5 6 7 8
| CABasicAnimation *rotationAniamtion = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"]; rotationAniamtion.toValue = @(M_PI*2); 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 画两条边框
这两个边框绘制方法是一模一样的,只是中间有个时间间隔而已
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
| - (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 - GCCircleRadius*2.4)]; [rectPath addLineToPoint:CGPointMake(startPoint.x + powf(3, 0.5)*GCCircleRadius*1.2, startPoint.y - GCCircleRadius*2.4)]; [rectPath addLineToPoint:CGPointMake(startPoint.x + powf(3, 0.5)*GCCircleRadius*1.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
这两个点来控制弧度方向,如图:
然后我们只需要交叉改变controlPoint1
和controlPoint2
这两个点在上下的位置和startPoint
和endPoint
的位置,就能感觉水面上涨的感觉
1 2 3 4 5
| NSMutableArray <UIBezierPath *> *waterPathArray = [NSMutableArray array]; for (NSInteger i = 0; i < 11; i++) { UIBezierPath *water = [self water:i % 2 == 0 ? YES : NO withProgress:0.1*i]; [waterPathArray addObject:water]; }
|
创建完毕path
之后,将anmations
放到CAAnimationGroup
里面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| - (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
即可
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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];
|
这个加载动画的缺点就是在加载时没有可定制化的形状,只能修改圆形等的颜色,如果要改变形状,可能会涉及到动效的改动,所以这个动画只能作为学习分析参考