手把手教你使用Core animation 做動(dòng)?畫

來源: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 投訴

寫留言

?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末鞋喇,一起剝皮案震驚了整個(gè)濱河市声滥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌侦香,老刑警劉巖落塑,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異罐韩,居然都是意外死亡憾赁,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門散吵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來龙考,“玉大人,你說我怎么就攤上這事矾睦』蘅睿” “怎么了?”我有些...
    開封第一講書人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵枚冗,是天一觀的道長缓溅。 經(jīng)常有香客問我,道長赁温,這世上最難降的妖魔是什么坛怪? 我笑而不...
    開封第一講書人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任淤齐,我火速辦了婚禮,結(jié)果婚禮上袜匿,老公的妹妹穿的比我還像新娘更啄。我一直安慰自己,他們只是感情好沉帮,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開白布锈死。 她就那樣靜靜地躺著贫堰,像睡著了一般穆壕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上其屏,一...
    開封第一講書人閱讀 51,598評(píng)論 1 305
  • 那天喇勋,我揣著相機(jī)與錄音,去河邊找鬼偎行。 笑死川背,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蛤袒。 我是一名探鬼主播熄云,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼妙真!你這毒婦竟也來了缴允?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤珍德,失蹤者是張志新(化名)和其女友劉穎练般,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體锈候,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡薄料,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了泵琳。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片摄职。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖获列,靈堂內(nèi)的尸體忽然破棺而出谷市,到底是詐尸還是另有隱情,我是刑警寧澤蛛倦,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布歌懒,位于F島的核電站,受9級(jí)特大地震影響溯壶,放射性物質(zhì)發(fā)生泄漏及皂。R本人自食惡果不足惜甫男,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望验烧。 院中可真熱鬧板驳,春花似錦、人聲如沸碍拆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽感混。三九已至端幼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間弧满,已是汗流浹背婆跑。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留庭呜,地道東北人滑进。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像募谎,于是被迫代替她去往敵國和親扶关。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容