一雳刺、動畫過程分析
1劫灶、拆分動畫
正常動畫效果如下:
操作 | 現(xiàn)象 |
---|---|
放慢動畫 |
|
添加輔助顏色 |
|
只保留豎線 |
|
只保留三角動畫 |
|
只保留弧線 |
|
2、總結(jié)
動畫是由兩部分組成:
1越庇、豎線在開始時的預(yù)備動畫和結(jié)束時的慣性動畫
2罩锐、豎線、三角形和弧線的銜接動畫
二卤唉、動畫開發(fā)
設(shè):暫停--->播放的狀態(tài)為正向,播放--->暫停為逆向涩惑。
創(chuàng)建一個坐標(biāo)系,這里設(shè)整個容器的邊長是a桑驱,坐標(biāo)系的原點為容器的左上角竭恬。
1、豎線的準(zhǔn)備和慣性動畫
為了更清晰的觀察熬的,預(yù)備動畫和其他動畫間做了1s的延時萍聊。
這個需要多看幾遍≡梦觯可以看出來:
左側(cè)豎線是經(jīng)歷了兩個過程:
第一個動畫:底部不變寿桨,上部分變短;第二個動畫:整體向上位移。
右側(cè)豎線也有兩個過程:
第一個動畫:整體向上位移亭螟;第二個動畫:底部不變挡鞍,頂部縮短。
起始狀態(tài) | 第一個過程結(jié)束狀態(tài) | 第二個過程結(jié)束狀態(tài) |
---|---|---|
1-1.png
|
1-2.png
|
1-3.png
|
根據(jù)上圖预烙,設(shè)左側(cè)豎線為L1,方向由上至下,右側(cè)豎線為L2方向由下至上墨微,通過貝塞爾曲線繪制豎線,需要確定豎線的起點和終點扁掸。
L1 起始位置起點為:(0.2a,0)終點為:(0.2a,a)
L1 第二個位置起點為:(0.2a,0.4a)終點為:(0.2a,a)
L1 第三個位置起點為:(0.2a,0.2a)終點為:(0.2a,0.8a)
L2 起始狀態(tài)起點為:(0.8a,a)終點為:(0.8a,0)
L2 第二個位置起點為:(0.8a,0.8a)終點為:(0.8a,-0.2a)
L2 第三個位置起點為:(0.8a,0.8a)終點為:(0.8a,0.2a)
準(zhǔn)備動畫就是將豎線居中變短的過程翘县,設(shè)豎線動畫總時長為:positionDuration
代碼如下:
//暫停-》播放豎線動畫
- (void)linePositiveAnimation {
CGFloat a = self.bounds.size.width;
//左側(cè)縮放動畫
UIBezierPath *leftPath1 = [UIBezierPath bezierPath];
[leftPath1 moveToPoint:CGPointMake(0.2*a,0.4*a)];
[leftPath1 addLineToPoint:CGPointMake(0.2*a,a)];
_leftLineLayer.path = leftPath1.CGPath;
[_leftLineLayer addAnimation:[self pathAnimationWithDuration:positionDuration/2] forKey:nil];
//右側(cè)豎線位移動畫
UIBezierPath *rightPath1 = [UIBezierPath bezierPath];
[rightPath1 moveToPoint:CGPointMake(0.8*a, 0.8*a)];
[rightPath1 addLineToPoint:CGPointMake(0.8*a,-0.2*a)];
_rightLineLayer.path = rightPath1.CGPath;
[_rightLineLayer addAnimation:[self pathAnimationWithDuration:positionDuration/2] forKey:nil];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, positionDuration/2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^(void){
//左側(cè)位移動畫
UIBezierPath *leftPath2 = [UIBezierPath bezierPath];
[leftPath2 moveToPoint:CGPointMake(a*0.2,a*0.2)];
[leftPath2 addLineToPoint:CGPointMake(a*0.2,a*0.8)];
_leftLineLayer.path = leftPath2.CGPath;
[_leftLineLayer addAnimation:[self pathAnimationWithDuration:positionDuration/2] forKey:nil];
//右側(cè)豎線縮放動畫
UIBezierPath *rightPath2 = [UIBezierPath bezierPath];
[rightPath2 moveToPoint:CGPointMake(a*0.8,a*0.8)];
[rightPath2 addLineToPoint:CGPointMake(a*0.8,a*0.2)];
_rightLineLayer.path = rightPath2.CGPath;
[_rightLineLayer addAnimation:[self pathAnimationWithDuration:positionDuration/2] forKey:nil];
});
}
而結(jié)束時的慣性動畫,無非就是豎線由短邊長的過程谴分,和預(yù)備動畫大同小異锈麸。
代碼如下:
//播放-》暫停豎線動畫
- (void)lineInverseAnimation {
CGFloat a = self.bounds.size.width;
//左側(cè)位移動畫
UIBezierPath *leftPath1 = [UIBezierPath bezierPath];
[leftPath1 moveToPoint:CGPointMake(0.2*a,0.4*a)];
[leftPath1 addLineToPoint:CGPointMake(0.2*a,a)];
_leftLineLayer.path = leftPath1.CGPath;
[_leftLineLayer addAnimation:[self pathAnimationWithDuration:positionDuration/2] forKey:nil];
//右側(cè)豎線位移動畫
UIBezierPath *rightPath1 = [UIBezierPath bezierPath];
[rightPath1 moveToPoint:CGPointMake(0.8*a, 0.8*a)];
[rightPath1 addLineToPoint:CGPointMake(0.8*a,-0.2*a)];
_rightLineLayer.path = rightPath1.CGPath;
[_rightLineLayer addAnimation:[self pathAnimationWithDuration:positionDuration/2] forKey:nil];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, positionDuration/2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^(void){
//左側(cè)豎線縮放動畫
UIBezierPath *leftPath2 = [UIBezierPath bezierPath];
[leftPath2 moveToPoint:CGPointMake(a*0.2,0)];
[leftPath2 addLineToPoint:CGPointMake(a*0.2,a)];
_leftLineLayer.path = leftPath2.CGPath;
[_leftLineLayer addAnimation:[self pathAnimationWithDuration:positionDuration/2] forKey:nil];
//右側(cè)豎線縮放動畫
UIBezierPath *rightPath2 = [UIBezierPath bezierPath];
[rightPath2 moveToPoint:CGPointMake(a*0.8,a)];
[rightPath2 addLineToPoint:CGPointMake(a*0.8,0)];
_rightLineLayer.path = rightPath2.CGPath;
[_rightLineLayer addAnimation:[self pathAnimationWithDuration:positionDuration/2] forKey:nil];
});
}
2、銜接動畫
2-1牺蹄、 原理分析
為了方便觀察忘伞,銜接動畫時長再放慢一倍
分析:在銜接動畫中,左側(cè)豎線是連接著弧線和三角形的沙兰,右側(cè)豎線連接著是底部的弧線氓奈。而且在整個過程中,只有三角形的動畫時貫穿全程的鼎天,時長最長舀奶,所以設(shè)整個銜接動畫總時長為animationDuration,也就是三角動畫的時長斋射。
正向動畫過程:
1育勺、三角形的動畫時全程執(zhí)行的,從開始一直到結(jié)束绩鸣;
2怀大、開始時同時變化的是右側(cè)的豎線纱兑,縮短到起點呀闻;
3、弧線是連接右側(cè)豎線和左側(cè)豎線的橋梁潜慎,當(dāng)右側(cè)豎線長度縮短為零時捡多,弧線運動到左側(cè)豎線的底部;這時開始縮短弧線铐炫,至左側(cè)豎線的底部垒手;
4、左側(cè)豎線在弧線縮短至其底部時也開始縮短倒信,最終長度縮短為零科贬,同時三角形繪制完成。
逆向過程:
仔細(xì)觀察可知,逆向動畫起始就是正向動畫的逆過程榜掌。
無論在繪制直線還是曲線的時候优妙,都是利用CAShapeLayer+UIBezierPath來實現(xiàn)的,而合理的利用strokeEnd屬性就可以實現(xiàn)正向和逆向動畫的互相轉(zhuǎn)換憎账。
動畫時長:
通過三角形的動畫時長可以來確定其他部分的時長:
右側(cè)豎線縮短時長:animationDuration/4
弧線縮放時長(兩次):animationDuration/4
左側(cè)豎線縮放時長:animationDuration/4
這樣四次動畫的時長加起來正好為三角動畫的時長套硼。
2-2、繪制三角形
通過圖可以看出來胞皱,三角形是順時針拐了三次彎回到的原點邪意。經(jīng)過了 A->B->C->D->A 五個點完成首尾相接的三角形。
這幾個點的坐標(biāo)分別為:(0.2a,0.2a)反砌、(0.2a,0)雾鬼、(a,0.5a)、(0.2a,a)于颖、(0.2a,0.2a)
繪制三角形代碼如下:
- (void)addTriangleLayer {
CGFloat a = self.bounds.size.width;
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(a*0.2,a*0.2)];
[path addLineToPoint:CGPointMake(a*0.2,0)];
[path addLineToPoint:CGPointMake(a,a*0.5)];
[path addLineToPoint:CGPointMake(a*0.2,a)];
[path addLineToPoint:CGPointMake(a*0.2,a*0.2)];
_triangleLayer = [CAShapeLayer layer];
_triangleLayer.path = path.CGPath;
_triangleLayer.fillColor = [UIColor clearColor].CGColor;
_triangleLayer.strokeColor = [UIColor redColor].CGColor;
_triangleLayer.lineWidth = [self lineWidth];
_triangleLayer.lineCap = kCALineCapButt;
_triangleLayer.lineJoin = kCALineJoinRound;
_triangleLayer.strokeEnd = 0;
[self.layer addSublayer:_triangleLayer];
}
2-3呆贿、 繪制弧線
由圖可知,弧線是從右側(cè)豎線的底部森渐,連接到左側(cè)豎線的底部做入;即從E點繞圓心O順時針到F點,角度為π。
代碼如下:
- (void)addCircleLayer {
CGFloat a = self.bounds.size.width;
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(a*0.8,a*0.8)];
[path addArcWithCenter:CGPointMake(a*0.5, a*0.8) radius:0.3*a startAngle:0 endAngle:M_PI clockwise:true];
_circleLayer = [CAShapeLayer layer];
_circleLayer.path = path.CGPath;
_circleLayer.fillColor = [UIColor clearColor].CGColor;
_circleLayer.strokeColor = [UIColor blueColor].CGColor;
_circleLayer.lineWidth = [self lineWidth];
_circleLayer.lineCap = kCALineCapRound;
_circleLayer.lineJoin = kCALineJoinRound;
_circleLayer.strokeEnd = 0;
[self.layer addSublayer:_circleLayer];
}
2-4同衣、 執(zhí)行動畫
在這個過程中動畫都是通過改變strokeEnd屬性實現(xiàn)的動畫竟块,為了方便,寫了一個通用的方法:
- (CABasicAnimation *)strokeEndAnimationFrom:(CGFloat)fromValue to:(CGFloat)toValue onLayer:(CALayer *)layer name:(NSString*)animationName duration:(CGFloat)duration delegate:(id)delegate {
CABasicAnimation *strokeEndAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
strokeEndAnimation.duration = duration;
strokeEndAnimation.fromValue = @(fromValue);
strokeEndAnimation.toValue = @(toValue);
strokeEndAnimation.fillMode = kCAFillModeForwards;
strokeEndAnimation.removedOnCompletion = NO;
[strokeEndAnimation setValue:animationName forKey:@"animationName"];
strokeEndAnimation.delegate = delegate;
[layer addAnimation:strokeEndAnimation forKey:nil];
return strokeEndAnimation;
}
執(zhí)行正向動畫代碼如下:
(弧線動畫一部分是通過改變strokeStart屬性實現(xiàn)的)
- (void)actionPositiveAnimation {
//開始三角動畫
[self strokeEndAnimationFrom:0 to:1 onLayer:_triangleLayer name:TriangleAnimation duration:animationDuration delegate:self];
//開始右側(cè)線條動畫
[self strokeEndAnimationFrom:1 to:0 onLayer:_rightLineLayer name:RightLineAnimation duration:animationDuration/4 delegate:self];
//開始畫弧動畫
[self strokeEndAnimationFrom:0 to:1 onLayer:_circleLayer name:nil duration:animationDuration/4 delegate:nil];
//開始逆向畫弧動畫
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, animationDuration*0.25 * NSEC_PER_SEC), dispatch_get_main_queue(), ^(void){
[self circleStartAnimationFrom:0 to:1];
});
//開始左側(cè)線條縮短動畫
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, animationDuration*0.5 * NSEC_PER_SEC), dispatch_get_main_queue(), ^(void){
[self strokeEndAnimationFrom:1 to:0 onLayer:_leftLineLayer name:nil duration:animationDuration/2 delegate:nil];
});
}
逆向動畫就是正向的逆過程耐齐,也就是strokeEnd屬性0到1和1到0的關(guān)系罷了浪秘,只是執(zhí)行時間不太一樣,就不貼代碼了埠况。
銜接動畫最終效果如下: