加载动画分析

原文地址:http://zeeyang.com/2016/07/27/loadingAnimation-0727/

好久没写动画了...最近扒了下以前没有写的动画效果,想想从最老的开始写吧,之前看到的版本是用Swift写的,没仔细找有没有OC版的,所以干脆自己练习一下吧,我们先来看看效果:
(这里三角形是旋转动画,但是Gif录出来看上去是抖了两下...)


可以直接run下代码,看下效果:https://github.com/Yuzeyang/GCLoadingAnimation/tree/master/GCLoadingAnimationOne

下面我来分析下过程

这个动画的实现只用到了UIBezierPathCABasicAnimationCALayer

从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设为circleLayerpath

self.circleLayer = [CAShapeLayer layer];

self.circleLayer.path = endPath.CGPath;

self.circleLayer.fillColor = [UIColor orangeColor].CGColor;
[self addSublayer:self.circleLayer];

然后加上动画,因为我们修改的是path,所以我们animationkeyPathpath(后面也是),设定起始值为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_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 画两条边框
这两个边框绘制方法是一模一样的,只是中间有个时间间隔而已
  • (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];

这个加载动画的缺点就是在加载时没有可定制化的�形状,只能修改圆形等的颜色,如果要改变形状,可能会涉及到动效的改动,所以这个动画只能作为学习分析参考
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容