來源:Airfei
鏈接:http://www.reibang.com/p/1e2b8ff3519e
最近在技術(shù)群里,有人發(fā)了一張帶有動(dòng)畫效果的圖片蜂大。覺得很有意思湿蛔,便動(dòng)手實(shí)現(xiàn)了一下。在這篇文章中你將會(huì)學(xué)到Core Animation顯式動(dòng)畫中的關(guān)鍵幀動(dòng)畫县爬、組合動(dòng)畫阳啥、CABasicAnimation動(dòng)畫。先上一張?jiān)瓐D的動(dòng)畫效果财喳。
本文要實(shí)現(xiàn)的效果圖如下:
把原動(dòng)畫gif動(dòng)畫在mac上使用圖片瀏覽模式打開察迟,我們可以看到動(dòng)畫每一幀的顯示。從每一幀上的展示過程耳高,可以把整體的動(dòng)畫進(jìn)行拆分成兩大部分扎瓶。
第一部分(Part1)從初始狀態(tài)變成取消狀態(tài)(圖片上是由橫實(shí)線變成上線橫線交叉的圓)。
第二部分(Part2)從取消狀態(tài)變回初始狀態(tài)泌枪。
下面我們先詳細(xì)分析Part1是怎么實(shí)現(xiàn)的概荷。根據(jù)動(dòng)畫圖,把Part1再細(xì)分成三步碌燕。
Step1 : 中間橫實(shí)線的由右向左的運(yùn)動(dòng)效果误证。這其實(shí)是一個(gè)組合動(dòng)畫。是先向左偏移的同時(shí)橫線變短修壕。先看一下實(shí)現(xiàn)的動(dòng)態(tài)效果愈捅。
向左偏移—使用基本動(dòng)畫中animationWithKeyPath鍵值對(duì)的方式來改變動(dòng)畫的值。我們這里使用position.x,同樣可以使用transform.translation.x來平移慈鸠。
改變橫線的大小—使用經(jīng)典的strokeStart和strokeEnd蓝谨。其實(shí)上橫線長度的變化的由strokeStart到strokeEnd之間的值來共同來決定。改變strokeEnd的值由1.0到0.4青团,不改變strokeStart的值譬巫。橫線的長度會(huì)從右側(cè)方向由1.0倍長度減少到0.4倍長度。參見示意圖的紅色區(qū)域督笆。
-(void) animationStep1{
//最終changedLayer的狀態(tài)
_changedLayer.strokeEnd = 0.4;
//基本動(dòng)畫芦昔,長度有1.0減少到0.4
CABasicAnimation *strokeAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
strokeAnimation.fromValue = [NSNumber numberWithFloat:1.0f];
strokeAnimation.toValue = [NSNumber numberWithFloat:0.4f];
//基本動(dòng)畫,向左偏移10個(gè)像素
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"position.x"];
pathAnimation.fromValue = [NSNumber numberWithFloat:0.0];
pathAnimation.toValue = [NSNumber numberWithFloat:-10];
//組合動(dòng)畫胖腾,平移和長度減少同時(shí)進(jìn)行
CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
animationGroup.animations = [NSArray arrayWithObjects:strokeAnimation,pathAnimation, nil];
animationGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
animationGroup.duration = kStep1Duration;
//設(shè)置代理
animationGroup.delegate = self;
animationGroup.removedOnCompletion = YES;
//監(jiān)聽動(dòng)畫
[animationGroup setValue:@"animationStep1" forKey:@"animationName"];
//動(dòng)畫加入到changedLayer上
[_changedLayer addAnimation:animationGroup forKey:nil];
}
Step2 : 由左向右的動(dòng)畫–向右偏移同時(shí)橫線長度變長烟零”袼桑看一下Step2要實(shí)現(xiàn)的動(dòng)畫效果咸作。其思路和Step1是一樣的锨阿。
-(void)animationStep2
{
? ?CABasicAnimation *translationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.translation.x"];
? ?translationAnimation.fromValue = [NSNumber numberWithFloat:-10];
? ?//strokeEnd:0.8 剩余的距離toValue = lineWidth * (1 - 0.8);
translationAnimation.toValue = [NSNumber numberWithFloat:0.2 * lineWidth ];
_changedLayer.strokeEnd = 0.8;
CABasicAnimation *strokeAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
strokeAnimation.fromValue = [NSNumber numberWithFloat:0.4f];
strokeAnimation.toValue = [NSNumber numberWithFloat:0.8f];
CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
animationGroup.animations = [NSArray arrayWithObjects:strokeAnimation,translationAnimation, nil];
animationGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
animationGroup.duration = kStep2Duration;
//設(shè)置代理
animationGroup.delegate = self;
animationGroup.removedOnCompletion = YES;
[animationGroup setValue:@"animationStep2" forKey:@"animationName"];
[_changedLayer addAnimation:animationGroup forKey:nil];
}
Step3: 圓弧的動(dòng)畫效果和上下兩個(gè)橫實(shí)線的動(dòng)畫效果。
畫圓弧记罚,首先想到是使用UIBezierPath墅诡。畫個(gè)示意圖來分析動(dòng)畫路徑。示意圖如下:
整個(gè)path路徑是由三部分組成桐智,ABC曲線末早、CD圓弧、DD′圓说庭。
使用UIBezierPath的方法
- (void)appendPath:(UIBezierPath *)bezierPath;
把三部分路徑關(guān)聯(lián)起來然磷。詳細(xì)講解思路。
? ABC曲線就是貝塞爾曲線刊驴,可以根據(jù)A姿搜、B、C三點(diǎn)的位置使用方法
//endPoint 終點(diǎn)坐標(biāo) controlPoint1 起點(diǎn)坐標(biāo)
//controlPoint2 起點(diǎn)和終點(diǎn)在曲線上的切點(diǎn)延伸相交的交點(diǎn)坐標(biāo)
- (void)addCurveToPoint:(CGPoint)endPoint
? ? ? ? ?controlPoint1:(CGPoint)controlPoint1
controlPoint2:(CGPoint)controlPoint2;
二次貝塞爾曲線示意圖如下:
其中control point 點(diǎn)是從曲線上取 start point和end point 切點(diǎn)相交匯的所得到的交點(diǎn)捆憎。如下圖:
首先C點(diǎn)取圓上的一點(diǎn)舅柜,-30°。那么
CGFloat angle = Radians(30);
C點(diǎn)坐標(biāo)為:
//C點(diǎn)
? ?CGFloat endPointX = self.center.x + Raduis * cos(angle);
? ?CGFloat endPointY = kCenterY - Raduis * sin(angle);
A點(diǎn)坐標(biāo)為:
//A點(diǎn) 取橫線最右邊的點(diǎn)
? ?CGFloat startPointX = self.center.x + lineWidth/2.0 ;
? ?CGFloat startPointY = controlPointY;
control point 為E點(diǎn):
//E點(diǎn) 半徑*反余弦(30°)
? ?CGFloat startPointX = self.center.x + Raduis *acos(angle);
? ?CGFloat startPointY = controlPointY;
? CD圓弧的路徑使用此方法確定
(instancetype)bezierPathWithArcCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise;
關(guān)于弧度問題躲惰,UIBezierPath的官方文檔中的這張圖:
StartAngle 弧度即C點(diǎn)弧度致份,EndAngel弧度即D點(diǎn)弧度。
CGFloat StartAngle = 2 * M_PI - angle;
CGFloat EndAngle = M_PI + angle;
? DD′圓的路徑和上面2一樣的方法確定础拨。
StartAngle 弧度即D點(diǎn)弧度氮块,EndAngel弧度即D′點(diǎn)弧度。
CGFloat StartAngle = M_PI *3/2 - (M_PI_2 -angle);
CGFloat EndAngle = -M_PI_2 - (M_PI_2 -angle);
下面部分代碼是所有path路徑诡宗。
UIBezierPath *path = [UIBezierPath bezierPath];
// 畫貝塞爾曲線 圓弧
[path moveToPoint:CGPointMake(self.center.x + ?lineWidth/2.0 , kCenterY)];
CGFloat angle = Radians(30);
//C點(diǎn)
CGFloat endPointX = self.center.x + Raduis * cos(angle);
CGFloat endPointY = kCenterY - Raduis * sin(angle);
//A點(diǎn)
CGFloat startPointX = self.center.x + lineWidth/2.0;
CGFloat startPointY = kCenterY;
//E點(diǎn) 半徑*反余弦(30°)
CGFloat controlPointX = self.center.x + Raduis *acos(angle);
CGFloat controlPointY = kCenterY;
//貝塞爾曲線 ABC曲線
[path addCurveToPoint:CGPointMake(endPointX, endPointY)
controlPoint1:CGPointMake(startPointX , startPointY)
controlPoint2:CGPointMake(controlPointX , controlPointY)];
// (360°- 30°) ->(180°+30°) 逆時(shí)針的圓弧 CD圓弧
UIBezierPath *path1 = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.center.x,kCenterY)
radius:Raduis
startAngle:2 * M_PI - angle
endAngle:M_PI + angle
clockwise:NO];
[path appendPath:path1];
// (3/2π- 60°) ->(-1/2π -60°) 逆時(shí)針的圓 DD′圓
UIBezierPath *path2 = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.center.x,kCenterY)
radius:Raduis
startAngle:M_PI *3/2 - (M_PI_2 -angle)
endAngle:-M_PI_2 - (M_PI_2 -angle)
clockwise:NO];
[path appendPath:path2];
_changedLayer.path = path.CGPath;
Path路徑有了雇锡,接著實(shí)現(xiàn)動(dòng)畫效果。
圓弧的長度逐漸變長僚焦。我們還是使用經(jīng)典的strokeStart和strokeEnd锰提。但是圓弧是如何變長的呢?
(1) 初始圓弧有一段長度芳悲。
(2) 在原始長度的基礎(chǔ)上逐漸變長立肘,逐漸遠(yuǎn)離A點(diǎn),同時(shí)要在D點(diǎn)停止名扛。
(3) 長度逐漸變長谅年,最終要在D與D′點(diǎn)交匯。
我們分別解決這個(gè)三個(gè)問題肮韧。
第一個(gè)問題融蹂,strokeEnd - strokeStart > 0這樣能保證有一段圓弧旺订。
第二個(gè)問題,逐漸變長,意味著strokeEnd值不斷變大超燃。遠(yuǎn)離A點(diǎn)意味著strokeStart的值不斷變大区拳。在D點(diǎn)停止,說明了strokeStart有上限值意乓。
第三個(gè)問題樱调,意味著strokeEnd值不斷變大,最終值為1.0届良。
這三個(gè)問題說明了一個(gè)問題笆凌,strokeEnd和strokeStart是一組變化的數(shù)據(jù)。
那么core animation 中可以控制一組值的動(dòng)畫是關(guān)鍵幀動(dòng)畫(CAKeyframeAnimation)士葫。
為了更準(zhǔn)確的給出strokeEnd和strokeStart值乞而,我們使用長度比來確定。
假設(shè)我們初始的長度就是曲線ABC的長度慢显。但是貝塞爾曲線長度怎么計(jì)算爪模?使用下面方法:
//求貝塞爾曲線長度
-(CGFloat) bezierCurveLengthFromStartPoint:(CGPoint)start toEndPoint:(CGPoint) end withControlPoint:(CGPoint) control
{
? ?const int kSubdivisions = 50;
? ?const float step = 1.0f/(float)kSubdivisions;
float totalLength = 0.0f;
CGPoint prevPoint = start;
// starting from i = 1, since for i = 0 calulated point is equal to start point
for (int i = 1; i <= kSubdivisions; i++)
{
float t = i*step;
float x = (1.0 - t)*(1.0 - t)*start.x + 2.0*(1.0 - t)*t*control.x + t*t*end.x;
float y = (1.0 - t)*(1.0 - t)*start.y + 2.0*(1.0 - t)*t*control.y + t*t*end.y;
CGPoint diff = CGPointMake(x - prevPoint.x, y - prevPoint.y);
totalLength += sqrtf(diff.x*diff.x + diff.y*diff.y); // Pythagorean
prevPoint = CGPointMake(x, y);
}
return totalLength;
}
計(jì)算貝塞爾曲線所在的比例為:
CGFloat orignPercent = [self calculateCurveLength]/[self calculateTotalLength];
初始的strokeStart = 0、strokeEnd = orignPercent鳍怨。
最終的stokeStart = 呻右?
//結(jié)果就是貝塞爾曲線長度加上120°圓弧的長度與總長度相比得到的結(jié)果。
CGFloat endPercent =([self calculateCurveLength] + Radians(120) *Raduis ) / [self calculateTotalLength];
實(shí)現(xiàn)動(dòng)畫的代碼為
CGFloat orignPercent = [self calculateCurveLength] / [self calculateTotalLength];
? ?CGFloat endPercent =([self calculateCurveLength] + Radians(120) *Raduis ) /[self calculateTotalLength];
_changedLayer.strokeStart = endPercent;
//方案1
CAKeyframeAnimation *startAnimation = [CAKeyframeAnimation animationWithKeyPath:@"strokeStart"];
startAnimation.values = @[@0.0,@(endPercent)];
CAKeyframeAnimation *EndAnimation = [CAKeyframeAnimation animationWithKeyPath:@"strokeEnd"];
EndAnimation.values = @[@(orignPercent),@1.0];
CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
animationGroup.animations = [NSArray arrayWithObjects:startAnimation,EndAnimation, nil];
animationGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
animationGroup.duration = kStep3Duration;
animationGroup.delegate = self;
animationGroup.removedOnCompletion = YES;
[animationGroup setValue:@"animationStep3" forKey:@"animationName"];
[_changedLayer addAnimation:animationGroup forKey:nil];
效果圖為:
閱讀 18148 投訴
寫留言
?