iOS核心動(dòng)畫-Core Animation
概論
目標(biāo):1. 學(xué)會(huì)使用圖層精簡(jiǎn)非交互式繪圖;2. 通過(guò)核心動(dòng)畫創(chuàng)建基礎(chǔ)動(dòng)畫,關(guān)鍵幀動(dòng)畫,動(dòng)畫組,轉(zhuǎn)場(chǎng)動(dòng)畫;3. 學(xué)習(xí)使用UIView對(duì)這些動(dòng)畫簡(jiǎn)化操作的裝飾方法.
1. CALayer
1.1 簡(jiǎn)介.
CALyer包含在QuartzCore框架中,這是一個(gè)跨平臺(tái)的框架(iOS 和 MAC OSX);在使用Core Animation開(kāi)發(fā)動(dòng)畫的本質(zhì)就是將CALayer中的內(nèi)容轉(zhuǎn)化為位圖從而供硬件操作;
在Core Animation中我們更多是直接操作圖層;UIView中有一個(gè)layer屬性作為根圖層,根圖層上可以放其他子圖層,UIView中所有能看到的內(nèi)容都包含在layer中;
1.2 CALayer常用屬性.
iOS中CALayer的設(shè)計(jì)主要是為了內(nèi)容展示和動(dòng)畫操作,它本身并不包含在UIKit中,所以不能響應(yīng)事件.CALyer設(shè)計(jì)之初就考慮它的動(dòng)畫操作功能,因此它的很多屬性在修改時(shí)都能形成動(dòng)畫效果,這種屬性稱為"隱式動(dòng)畫屬性",很多開(kāi)發(fā)的時(shí)候做平移動(dòng)畫直接使用隱式動(dòng)畫即可;
但是,對(duì)于UIView的根圖層而言,屬性修改不會(huì)形成動(dòng)畫效果,因?yàn)楹芏嗲闆r下根圖層是充當(dāng)容器的作用,如果的它的屬性變動(dòng)形成的動(dòng)畫效果會(huì)直接影響子圖層.而且根視圖的創(chuàng)建工作完全由系統(tǒng)完成,無(wú)法重新創(chuàng)建,只能往根視圖中添加或移除子圖層.層級(jí)結(jié)構(gòu)如下:

列表CALyer的常用屬性如下:
- 隱式動(dòng)畫的本質(zhì)是這些屬性的變動(dòng)默認(rèn)隱含了CABasicAnimation動(dòng)畫實(shí)現(xiàn).參考Xcode文檔“Animatable Properties”一節(jié)
- CALayer中很少使用frame屬性,應(yīng)為frame本身不支持動(dòng)畫效果,使用bounds和position代替.
- CALayer中使用opacity表示透明度;
- anchorPoint是圖層錨點(diǎn): 決定CALayer身上哪個(gè)點(diǎn)在position屬性所指的位置 .以自身左上角為原點(diǎn),范圍0到1;默認(rèn)是[0.5,0.5];
- (面試題)CALayer為是么使用CGImageRef和CGColorRef?--> QuartzCore是跨平臺(tái)的框架,所以不能使用UIKit中的UIColor;
- 默認(rèn)每個(gè)動(dòng)畫都會(huì)包裝一個(gè)事務(wù),因此執(zhí)行隱式動(dòng)畫之前可以設(shè)置這個(gè)動(dòng)畫時(shí)長(zhǎng): [CATransaction setAnimation:1]
擴(kuò)展:若有特殊需求,要關(guān)閉隱式動(dòng)畫-需要用到動(dòng)畫事務(wù)CATransaction,在事務(wù)內(nèi)將隱式動(dòng)畫關(guān)閉
//開(kāi)啟事務(wù)
[CATransaction begin];
//禁用隱式動(dòng)畫
[CATransaction setDisableActions:YES];
在這部分更改圖層屬性.將不會(huì)產(chǎn)生隱式動(dòng)畫
//提交事務(wù)
[CATransaction commit];
1.3 CALayer繪圖
上一章中使用的Quartz 2D繪圖其實(shí)已經(jīng)用到了CALayer,當(dāng)利用drawRect:方法繪圖的本質(zhì)就是繪制到圖層中,只是drawRect: 是由UIKit組件進(jìn)行調(diào)用,所以里面可以使用一些UIKit封裝的方法進(jìn)行繪圖;
而直接繪制到圖層的方法由于并非UIKit直接調(diào)用,因此只能用原生的Core Graphics方法繪制.
圖層繪圖有兩種方法,不管哪種繪制完成都必須調(diào)用圖層的setNeedDisplay方法 (注意:圖層的方法,不是上一章UIView的方法.)
- 通過(guò)圖層代理 drawLayer: inContext: 方法.
- 通過(guò)自定義圖層的 drawInContext: 方法;
1.3.1 代理方法繪圖
步驟:
- 指定圖層的代理,所有NSObject都遵守協(xié)議,所以不用手動(dòng)實(shí)現(xiàn)CALayerDelegate;
- 在代理對(duì)象中重寫-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;
- 調(diào)用圖層的setNeedsDisplay方法;
代碼:
#define PHOTO_HEIGHT 50
//自定義圖層
CALayer *Layer = [[CALayer alloc] init];
Layer.bounds = CGRectMake (0,0,PHOTO_HEIGHT,PHOTO_HEIGHT);
layer.position = CGPointMake (160,200);
layer.backgroundColor = [UIColor redColor].CGColor;
layer,cornerRadius = 50/2;
//僅僅設(shè)置圓角,對(duì)于圖形而言可以正常顯示,但是對(duì)于圖層中繪制的圖片無(wú)法正常顯示,需要:
layer.masksToBounds = YES; 來(lái)裁剪超出圖層部分 才能顯示出圓角;
//但是同理:陰影效果無(wú)法和masksToBounds 同時(shí)使用.陰影也會(huì)被剪掉;
//設(shè)置邊框
layer.borderColor = [UIColor whiteColor].CGColor;
layer.borderWidth = 2;
//添加到根圖層
[self.view.layer addSublayer: layer ]
//1. 設(shè)置圖層代理
layer.delegate = self;
//2. 調(diào)用
[layer setNeedsDisplay];
//3. 實(shí)現(xiàn)代理方法
- (void) -(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
// 傳入的layer是上面定義的圖層 圖形上下文也是此圖層的上下文.
//保存繪圖狀態(tài)
CGContestSaveGState(cox);
//圖形上下文形變,解決圖片倒立 問(wèn)題 //UIKit 坐標(biāo)系與 Core Graphics 坐標(biāo)系不同;
CGContextScaleCTM (ctx, 1, -1);
CGContextTranslateCTM(cox, 0, -PHOTO_HEIGHT);
UIImage *image = [UIImage imageName:@"photo.png"];
//繪圖 注意:這個(gè)位置時(shí)相對(duì)于圖層 不是屏幕;
CGContextDrawImage(ctx, CGRectMake(0, 0, PHOTO_HEIGHT, PHOTO_HEIGHT), image.CGImage);
CGContextRestoreGState(ctx);
}
擴(kuò)展1.帶陰影效果的圖片裁剪:
由于上面的矛盾,兩個(gè)設(shè)置不能同時(shí)顯示;換個(gè)思路:使用兩個(gè)圖層,下面的用來(lái)繪制陰影作為容器圖層,上面的用來(lái)顯示圖片,裁剪上面的即可;
擴(kuò)展2.圖層的形變:
- 從上面代碼中大家不難發(fā)現(xiàn)使用Core Graphics繪制圖片時(shí)會(huì)倒立顯示,上面是使用圖形上下文形變來(lái)解決這個(gè)問(wèn)題.其實(shí)設(shè)置繞x軸旋轉(zhuǎn)180度同樣可以達(dá)到正確顯示目的,只是用圖形上下文做不到旋轉(zhuǎn);
- 但是其實(shí)圖層有個(gè)transform屬性可以直接設(shè)置旋轉(zhuǎn),不需要借助圖形上下文,需要注意的是transform是CATransform3D類型,形變可以在三個(gè)維度上進(jìn)行;(如:CATransform3DMakeTranslation( )、CATransform3DMakeScale( )、CATransform3DMakeRotation( ))。
//利用圖層形變解決圖像倒立問(wèn)題 layer.transform = CATransform3DMakeRotation(M_PI, 1, 0, 0); - layer默認(rèn)照錨點(diǎn)旋轉(zhuǎn), 3D向量旋轉(zhuǎn)CATransFrom3DMakeRotation; 后三個(gè)是三維坐標(biāo)(x,y,z); xy面作為手機(jī)屏幕面.
- 既然如此,為什么還要說(shuō)圖層的形變,直接設(shè)置transform不就行了么;因?yàn)樾巫儗?duì)于動(dòng)畫有特殊意義.在動(dòng)畫開(kāi)發(fā)中形變往往不是直接設(shè)置transform,而是通過(guò)keyPath設(shè)置,這KVC設(shè)置方法和前面沒(méi)有區(qū)別,但是這種方式在動(dòng)畫中很實(shí)用,因?yàn)樗梢院芊奖愕膶追N形變組合到一起使用,
例:[layer setValue:@M_PI forKeyPath:@"transform.rotation.x"];具體屬性可查詢文檔“CATransform3D Key Paths”一節(jié).
1.3.2 使用自定義圖層繪圖
步驟:
- 自定義繼承CALyer的類,
- 在這個(gè)類.m中實(shí)現(xiàn)drawInContext: 方法 進(jìn)行繪圖;
- 在UIView中新建這個(gè)類對(duì)象 調(diào)用圖層 setNeedsDisplay即可顯示出繪圖;
如下:
//1. 創(chuàng)建繼承CALayer的KALayer類;
//2. 在KALayer中重寫drawInContext:(CGContextRef)ctx 進(jìn)行具體繪圖
-(void)drawInContext:(CGContextRef)ctx{
//打印一下上下文地址
NSLog(@"CGContext:%@",ctx);
//// 開(kāi)始畫圖
CGContextMoveToPoint(ctx, 94.5, 33.5);
CGContextAddLineToPoint(ctx,104.02, 47.39);
CGContextClosePath(ctx);
CGContextSetRGBFillColor(ctx, 135.0/255.0, 232.0/255.0, 84.0/255.0, 1);
CGContextSetRGBStrokeColor(ctx, 135.0/255.0, 232.0/255.0, 84.0/255.0, 1);
CGContextDrawPath(ctx, kCGPathFillStroke);
}
//3. 在view文件中.
-(instancetype)initWithFrame:(CGRect)frame{
NSLog(@"initWithFrame:");
if (self=[super initWithFrame:frame]) {
KCLayer *layer=[[KCLayer alloc]init];
//確定layer位置大小即可
layer.bounds=CGRectMake(0, 0, 185, 185);
layer.position=CGPointMake(160,284);
layer.backgroundColor=[UIColor colorWithRed:0 green:146/255.0 blue:1.0 alpha:1.0].CGColor;
//顯示圖層
[layer setNeedsDisplay];
[self.layer addSublayer:layer];
}
return self;
}
//驗(yàn)證 :drawRect:方法繪圖的本質(zhì)就是繪制到圖層中;
//這里能調(diào)用是應(yīng)為UIView創(chuàng)建圖層會(huì)自動(dòng)設(shè)置圖層代理為其自身
- (void)drawLayer:(CALayer *)layer inContext(CGContextRef)ctx{
[super drawLayer:layer inContext:ctx];
//打印上下文
NSLog(@"CGContext:%@",ctx);
}
- (void)drawRect:(CGRect) rect {
[super drawRect:rect]
// 我們?cè)谶@里獲取到的當(dāng)前圖形上下文正是drawLayer:中傳遞的
NSLog(@"CGContext:%@",UIGraphicsGetCurrentContext())
}
UIView在顯示時(shí)其根圖層會(huì)自動(dòng)創(chuàng)建一個(gè)CGContextRef(CALayer本質(zhì)使用的是位圖上下文),同時(shí)調(diào)用圖層代理(UIView創(chuàng)建圖層會(huì)自動(dòng)設(shè)置圖層代理為其自身,引申的,不能給給自定義layer的代理設(shè)為view,應(yīng)為view已經(jīng)做自己根圖層的代理了)的draw: inContext:方法并將圖形上下文作為參數(shù)傳遞給這個(gè)方法。而在UIView的draw:inContext:方法中會(huì)調(diào)用其drawRect:方法,在drawRect:方法中使用UIGraphicsGetCurrentContext()方法得到的上下文正是前面創(chuàng)建的上下文。
2. Core Animation 核心動(dòng)畫
簡(jiǎn)介.
在iOS中實(shí)現(xiàn)一個(gè)動(dòng)畫相當(dāng)簡(jiǎn)單,只要調(diào)用UIView的塊代碼即可實(shí)現(xiàn)一個(gè)動(dòng)畫效果,使用上面UIView封裝的方法進(jìn)行動(dòng)畫設(shè)置固然十分方便,但是具體動(dòng)畫如何實(shí)現(xiàn)我們是不清楚的,而且上面的代碼還有一些問(wèn)題是無(wú)法解決的,例如:如何控制動(dòng)畫的暫停?如何進(jìn)行動(dòng)畫的組合?。。。這就需要了解核心動(dòng)畫.

核動(dòng)畫分類:
-
CAAnimation: 核心動(dòng)畫基礎(chǔ)類,不直接使用;負(fù)責(zé)動(dòng)畫 運(yùn)行時(shí)間,速度的控制 ,本身實(shí)現(xiàn)了CAMediaTiming協(xié)議;
-
CAPropertyAnimation: 隱式動(dòng)畫的基類,不能直接使用;
- CABasicAnimation:基礎(chǔ)動(dòng)畫,通過(guò)屬性修改進(jìn)行動(dòng)畫參數(shù)控制,只有初始狀態(tài)和結(jié)束狀態(tài)。
- CAKeyframeAnimation:關(guān)鍵幀動(dòng)畫,同樣是通過(guò)屬性進(jìn)行動(dòng)畫參數(shù)控制,但是同基礎(chǔ)動(dòng)畫不同的是它可以有多個(gè)狀態(tài)控制。
- CAAnimationGroup:動(dòng)畫組,動(dòng)畫組是一種組合模式設(shè)計(jì),可以通過(guò)動(dòng)畫組來(lái)進(jìn)行所有動(dòng)畫行為的統(tǒng)一控制,組中所有動(dòng)畫效果可以并發(fā)執(zhí)行。
- CATransition:轉(zhuǎn)場(chǎng)動(dòng)畫,主要通過(guò)濾鏡進(jìn)行動(dòng)畫效果設(shè)置。
-
CAPropertyAnimation: 隱式動(dòng)畫的基類,不能直接使用;
基礎(chǔ)動(dòng)畫/關(guān)鍵幀動(dòng)畫都是屬于屬性動(dòng)畫,開(kāi)發(fā)人員只需要設(shè)置初始值和結(jié)束值,中間的過(guò)程動(dòng)畫(又叫“補(bǔ)間動(dòng)畫”)由系統(tǒng)自動(dòng)計(jì)算產(chǎn)生。和基礎(chǔ)動(dòng)畫不同的是關(guān)鍵幀動(dòng)畫可以設(shè)置多個(gè)屬性值,每?jī)蓚€(gè)屬性中間的補(bǔ)間動(dòng)畫由系統(tǒng)自動(dòng)完成,因此從這個(gè)角度而言基礎(chǔ)動(dòng)畫又可以看成是只有兩個(gè)關(guān)鍵幀的關(guān)鍵幀動(dòng)畫。
2.1 基礎(chǔ)動(dòng)畫CABasicAnimation.
很多情況通過(guò)基礎(chǔ)動(dòng)畫就能滿足要求.如果不使用UIView的封裝后方法,一般步驟:
- 初始化動(dòng)畫并指定動(dòng)畫屬性;決定了執(zhí)行怎樣的動(dòng)畫,是調(diào)整哪個(gè)屬性來(lái)執(zhí)行動(dòng)畫;
- 設(shè)置動(dòng)畫初始值結(jié)束值和其他屬性.
- 初始值一般省略,默認(rèn)圖層初始狀態(tài).
- 一般使用KVC,
[self.redView.layer setValue:@0.5 forKeyPath:@"transform.scale"]; - 其他屬性:duration時(shí)間,repeatCount次數(shù)(設(shè)為HUGE_VALF表示循環(huán)動(dòng)畫效果);
- 添加動(dòng)畫到圖層.
示例代碼如下:
//1.創(chuàng)建動(dòng)畫并指定動(dòng)畫屬性
CABasicAnimation *basicAnimation=[CABasicAnimation animationWithKeyPath:@"position"];
//2.設(shè)置動(dòng)畫屬性初始值和結(jié)束值
// basicAnimation.fromValue=[NSNumber numberWithInteger:50];//可以不設(shè)置,默認(rèn)為圖層初始狀態(tài)
//toValue表示最終到哪個(gè)值 byValue 相對(duì)于上一次增加多少值
basicAnimation.toValue=[NSValue valueWithCGPoint:location];
//設(shè)置其他動(dòng)畫屬性
basicAnimation.duration=5.0;//動(dòng)畫時(shí)間5秒
// basicAnimation.repeatCount=HUGE_VALF;//設(shè)置重復(fù)次數(shù),HUGE_VALF可看做無(wú)窮大,起到循環(huán)動(dòng)畫的效果
// 設(shè)置不反彈-兩者不可缺一
// basicAnimation.removedOnCompletion=NO;//運(yùn)行完畢一次后不要移除動(dòng)畫
// basicAnimation.fillMode= KCAFillModeForwards //保存最新模式
//3.添加動(dòng)畫到圖層,注意key相當(dāng)于給動(dòng)畫進(jìn)行命名,以后獲得該動(dòng)畫時(shí)可以使用此名稱獲取
[_layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Translation"];
存在問(wèn)題:動(dòng)畫結(jié)束后動(dòng)畫圖層回到了原來(lái)的位置,當(dāng)然使用UIView封裝的方法是沒(méi)這個(gè)問(wèn)題的;問(wèn)題的原因:圖層動(dòng)畫的本質(zhì)就是將圖層內(nèi)部的內(nèi)容轉(zhuǎn)化為位圖經(jīng)硬件操作形成一種動(dòng)畫效果,其實(shí)圖層本身并沒(méi)有任何的變化,動(dòng)畫效果是假象;
擴(kuò)展:動(dòng)畫的暫停與恢復(fù)
核心動(dòng)畫的運(yùn)行有一個(gè)媒體時(shí)間的概念:假設(shè)將一個(gè)旋轉(zhuǎn)動(dòng)畫設(shè)置旋轉(zhuǎn)一周要用時(shí)60秒,那么當(dāng)都動(dòng)畫旋轉(zhuǎn)90度后媒體時(shí)間就是15秒. 此時(shí)如果要將動(dòng)畫暫停,只需要讓媒體時(shí)間偏離量設(shè)置為15秒,然后動(dòng)畫運(yùn)行速度設(shè)置為0時(shí)期停止運(yùn)動(dòng);
類似的,如果暫停了50秒后需要恢復(fù)動(dòng)畫(此時(shí)媒體時(shí)間為65秒); 這時(shí)只要將動(dòng)畫開(kāi)始時(shí)間設(shè)置為當(dāng)前媒體時(shí)間65秒減去暫停時(shí)的時(shí)刻(之前的設(shè)置的偏移量)即(75-15 =50),即暫停的時(shí)時(shí)長(zhǎng),與此同時(shí)將偏移量重置為0,運(yùn)行速度設(shè)為1;
這個(gè)過(guò)程中真正起到暫停作用的是動(dòng)畫速度;媒體事件偏移量以及恢復(fù)時(shí)的開(kāi)始事件設(shè)置主要是為了讓動(dòng)畫更加連貫,不會(huì)閃跳;
#pragma mark 動(dòng)畫暫停
-(void)animationPause{
//取得指定圖層動(dòng)畫的媒體時(shí)間,后面參數(shù)用于指定子圖層,這里不需要
CFTimeInterval interval=[_layer convertTime:CACurrentMediaTime() fromLayer:nil];
//設(shè)置時(shí)間偏移量,保證暫停時(shí)停留在旋轉(zhuǎn)的位置
[_layer setTimeOffset:interval];
//速度設(shè)置為0,暫停動(dòng)畫
_layer.speed=0;
}
#pragma mark 動(dòng)畫恢復(fù)
-(void)animationResume{
//獲得暫停的時(shí)間
CFTimeInterval beginTime= CACurrentMediaTime()- _layer.timeOffset;
//設(shè)置偏移量
_layer.timeOffset=0;
//設(shè)置開(kāi)始時(shí)間
_layer.beginTime=beginTime;
//設(shè)置動(dòng)畫速度,開(kāi)始運(yùn)動(dòng)
_layer.speed=1.0;
}
注意:動(dòng)畫暫停針對(duì)的是圖層而不是圖層中的某個(gè)動(dòng)畫。;
2.2 關(guān)鍵幀動(dòng)畫CAKeyframeAnimation.
使用類似于基礎(chǔ)動(dòng)畫,但可以在無(wú)數(shù)個(gè)值(稱為關(guān)鍵幀)間改變.有個(gè)屬性values數(shù)組,用來(lái)接受這些值;
關(guān)鍵幀動(dòng)畫開(kāi)發(fā)分為兩種形式:一種是通過(guò)設(shè)置不同的屬性值進(jìn)行關(guān)鍵幀控制,另一種是通過(guò)繪制路徑進(jìn)行關(guān)鍵幀控制。后者優(yōu)先級(jí)高于前者,如果設(shè)置了路徑則屬性值就不再起作用。
屬性關(guān)鍵幀
//1.創(chuàng)建關(guān)鍵幀動(dòng)畫并設(shè)置動(dòng)畫屬性
CAKeyframeAnimation *keyframeAnimation=[CAKeyframeAnimation animationWithKeyPath:@"position"];
//2.設(shè)置關(guān)鍵幀,這里有四個(gè)關(guān)鍵幀
NSValue *key1=[NSValue valueWithCGPoint:_layer.position];//對(duì)于關(guān)鍵幀動(dòng)畫初始值不能省略
NSValue *key2=[NSValue valueWithCGPoint:CGPointMake(80, 220)];
NSValue *key3=[NSValue valueWithCGPoint:CGPointMake(45, 300)];
NSValue *key4=[NSValue valueWithCGPoint:CGPointMake(55, 400)];
NSArray *values=@[key1,key2,key3,key4];
keyframeAnimation.values=values;
//設(shè)置其他屬性
keyframeAnimation.duration=8.0;
keyframeAnimation.beginTime=CACurrentMediaTime()+2;//設(shè)置延遲2秒執(zhí)行
//3.添加動(dòng)畫到圖層,添加動(dòng)畫后就會(huì)執(zhí)行動(dòng)畫
[_layer addAnimation:keyframeAnimation forKey:@"KCKeyframeAnimation_Position"];
路徑關(guān)鍵幀
//1.創(chuàng)建關(guān)鍵幀動(dòng)畫并設(shè)置動(dòng)畫屬性
CAKeyframeAnimation *keyframeAnimation=[CAKeyframeAnimation animationWithKeyPath:@"position"];
//2.設(shè)置路徑
//繪制貝塞爾曲線
CGPathRef path=CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, _layer.position.x, _layer.position.y);//移動(dòng)到起始點(diǎn)
CGPathAddCurveToPoint(path, NULL, 160, 280, -30, 300, 55, 400);//繪制二次貝塞爾曲線
keyframeAnimation.path=path;//設(shè)置path屬性
CGPathRelease(path);//釋放路徑對(duì)象
//設(shè)置其他屬性
keyframeAnimation.duration=8.0;
keyframeAnimation.beginTime=CACurrentMediaTime()+2;//設(shè)置延遲2秒執(zhí)行
//3.添加動(dòng)畫到圖層,添加動(dòng)畫后就會(huì)執(zhí)行動(dòng)畫
[_layer addAnimation:keyframeAnimation forKey:@"KCKeyframeAnimation_Position"];
不同動(dòng)畫效果如下:
<img src="http://images.cnitblog.com/blog/62046/201409/150628429879513.png
" width = "120" height = "200" alt="圖片名稱" align=center />
<img src="http://images.cnitblog.com/blog/62046/201409/150628441904812.png
" width = "120" height = "200" alt="路徑幀" align=center />
補(bǔ)充-其他屬性
keyTimes:各個(gè)關(guān)鍵幀的時(shí)間控制,值是各關(guān)鍵幀所占設(shè)置的動(dòng)畫時(shí)間的比例,(可以大于1);
caculationMode:動(dòng)畫計(jì)算模式.拿上面keyValues動(dòng)畫舉例,之所以1到2幀能形成連貫性動(dòng)畫而不是直接從第1幀經(jīng)過(guò)8/3秒到第2幀是因?yàn)閯?dòng)畫模式是連續(xù)的(值為kCAAnimationLinear,這是計(jì)算模式的默認(rèn)值);而如果指定了動(dòng)畫模式為kCAAnimationDiscrete離散的那么你會(huì)看到動(dòng)畫從第1幀經(jīng)過(guò)8/3秒直接到第2幀,中間沒(méi)有任何過(guò)渡。其他動(dòng)畫模式還有:kCAAnimationPaced(均勻執(zhí)行,會(huì)忽略keyTimes)、kCAAnimationCubic(平滑執(zhí)行,對(duì)于位置變動(dòng)關(guān)鍵幀動(dòng)畫運(yùn)行軌跡更平滑)、kCAAnimationCubicPaced(平滑均勻執(zhí)行)。
下圖描繪出了幾種動(dòng)畫模式的關(guān)系(橫坐標(biāo)是運(yùn)行時(shí)間,縱坐標(biāo)是動(dòng)畫屬性[例如位置、透明度等]):
<img src="http://images.cnitblog.com/blog/62046/201409/150628454405097.png
" width = "300" height = "200" alt="圖片名稱" align=center />
2.3 動(dòng)畫組CAAnimationGroup.
實(shí)際開(kāi)發(fā)中一個(gè)物體的運(yùn)動(dòng)往往是復(fù)合運(yùn)動(dòng),單一屬性的運(yùn)動(dòng)情況比較少,但恰恰屬性動(dòng)畫每次進(jìn)行動(dòng)畫設(shè)置時(shí)一次只能設(shè)置一個(gè)屬性進(jìn)行動(dòng)畫控制,這樣一來(lái)要做一個(gè)復(fù)合運(yùn)動(dòng)的動(dòng)畫就必須創(chuàng)建多個(gè)屬性動(dòng)畫進(jìn)行組合。動(dòng)畫組的產(chǎn)生就是基于這樣一種情況而產(chǎn)生的.
動(dòng)畫組是一系列動(dòng)畫的組合,凡是添加到動(dòng)畫組中的動(dòng)畫都受控于動(dòng)畫組,這樣一來(lái)各類動(dòng)畫公共的行為就可以統(tǒng)一進(jìn)行控制而不必單獨(dú)設(shè)置,而且放到動(dòng)畫組中的各個(gè)動(dòng)畫可以并發(fā)執(zhí)行,共同構(gòu)建出復(fù)雜的動(dòng)畫效果。
一般步驟:首先單獨(dú)創(chuàng)建單個(gè)動(dòng)畫(可以是基礎(chǔ)動(dòng)畫也可以是關(guān)鍵幀動(dòng)畫),之后添加到動(dòng)畫組上,再把動(dòng)畫組添加到圖層,就可以統(tǒng)一,同時(shí)執(zhí)行這些動(dòng)畫.
//1.創(chuàng)建動(dòng)畫組
CAAnimationGroup *animationGroup=[CAAnimationGroup animation];
//2.設(shè)置組中的動(dòng)畫和其他屬性
CABasicAnimation *basicAnimation=[self rotationAnimation]; //是封裝上面的基礎(chǔ)動(dòng)畫代碼
CAKeyframeAnimation *keyframeAnimation=[self translationAnimation];//封裝上面幀動(dòng)畫代碼
animationGroup.animations=@[basicAnimation,keyframeAnimation];
animationGroup.duration=10.0;//設(shè)置動(dòng)畫時(shí)間,如果動(dòng)畫組中動(dòng)畫已經(jīng)設(shè)置過(guò)動(dòng)畫屬性則不再生效
animationGroup.beginTime=CACurrentMediaTime()+5;//延遲五秒執(zhí)行
//3.給圖層添加動(dòng)畫
[_layer addAnimation:animationGroup forKey:nil];
2.4 轉(zhuǎn)場(chǎng)動(dòng)畫CATransition
蘋果封裝的一個(gè)場(chǎng)景以動(dòng)畫形式轉(zhuǎn)到另一個(gè)場(chǎng)景.
步驟:
- 創(chuàng)建轉(zhuǎn)場(chǎng)動(dòng)畫
- 設(shè)置轉(zhuǎn)場(chǎng)類型,子類型(可選)以及其他屬性;
- 設(shè)置轉(zhuǎn)場(chǎng)后的新視圖并添加動(dòng)畫到圖層.
下表列出了常用的轉(zhuǎn)場(chǎng)類型(注意私有API是蘋果官方?jīng)]有公開(kāi)的動(dòng)畫類型,但是目前通過(guò)字符串仍然可以使用):
| 動(dòng)畫類型 | 說(shuō)明 | 對(duì)應(yīng)常量 | 是否支持方向設(shè)置 |
|---|---|---|---|
| 公開(kāi)API | |||
| fade | 淡出效果 | kCATransitionFade | 是 |
| movein | 新視圖移動(dòng)到舊視圖上 | kCATransitionMoveIn | 是 |
| push | 新視圖推出舊視圖 | kCATransitionPush | 是 |
| reveal | 移開(kāi)舊視圖顯示新視圖 | kCATransitionReveal | 是 |
| 私有API | 私有API只能通過(guò)字符串訪問(wèn) | ||
| cube | 立方體翻轉(zhuǎn)效果 | 無(wú) | 是 |
| oglFlip | 翻轉(zhuǎn)效果 | 無(wú) | 是 |
| suckEffect | 收縮效果 無(wú) | 否 | |
| rippleEffect | 水滴波紋效果 | 無(wú) | 否 |
| pageCurl | 向上翻頁(yè)效果 | 無(wú) | 是 |
| pageUnCurl | 向下翻頁(yè)效果 | 無(wú) | 是 |
| cameralIrisHollowOpen | 攝像頭打開(kāi)效果 | 無(wú) | 否 |
| cameraIrisHollowClose | 攝像頭關(guān)閉效果 | 無(wú) | 否 |
另外對(duì)于支持方向設(shè)置的動(dòng)畫類型還包含子類型:
| 動(dòng)畫子類型 | 說(shuō)明 |
|---|---|
| kCATransitionFromRight | 從右側(cè)轉(zhuǎn)場(chǎng) |
| kCATransitionFromLeft | 從左側(cè)轉(zhuǎn)場(chǎng) |
| kCATransitionFromTop | 從頂部轉(zhuǎn)場(chǎng) |
| kCATransitionFromBottom | 從底部轉(zhuǎn)場(chǎng) |
//1.創(chuàng)建轉(zhuǎn)場(chǎng)動(dòng)畫對(duì)象
CATransition *transition=[[CATransition alloc]init];
//2.設(shè)置動(dòng)畫類型,注意對(duì)于蘋果官方?jīng)]公開(kāi)的動(dòng)畫類型只能使用字符串,并沒(méi)有對(duì)應(yīng)的常量定義
transition.type=@"cube";
//設(shè)置子類型
if (isNext) {
transition.subtype=kCATransitionFromRight;
}else{
transition.subtype=kCATransitionFromLeft;
}
//設(shè)置動(dòng)畫時(shí)常
transition.duration=1.0f;
//3.設(shè)置轉(zhuǎn)場(chǎng)后的新視圖 添加轉(zhuǎn)場(chǎng)動(dòng)畫
_imageView.image=[self getImage:isNext];
[_imageView.layer addAnimation:transition forKey:@"KCTransitionAnimation"];
2.5 UIImageView的序列幀動(dòng)畫.
通過(guò)設(shè)置UIImageView的animationImages屬性,然后調(diào)用它的startAnimating方法去播放這組圖片。(存在著很大的性能問(wèn)題,并且這種方法一旦設(shè)置完圖片中間的過(guò)程就無(wú)法控制了。TOM貓例)
但是對(duì)于一些事物的運(yùn)動(dòng)又不得不選擇使用逐幀動(dòng)畫,例如人得運(yùn)動(dòng),這是一個(gè)高度復(fù)雜的運(yùn)動(dòng),基本動(dòng)畫、關(guān)鍵幀動(dòng)畫是不可能解決的。所大家一定要注意在循環(huán)方法中盡可能的降低算法復(fù)雜度,同時(shí)保證循環(huán)過(guò)程中內(nèi)存峰值盡可能低(延遲釋放)。
可以和其他動(dòng)畫組合使用.
3. UIView動(dòng)畫封裝
UIView本身對(duì)于基本動(dòng)畫和關(guān)鍵幀動(dòng)畫、轉(zhuǎn)場(chǎng)動(dòng)畫都有相應(yīng)的封裝,在對(duì)動(dòng)畫細(xì)節(jié)沒(méi)有特殊要求的情況下使用起來(lái)也要簡(jiǎn)單的多??梢哉f(shuō)在日常開(kāi)發(fā)中90%以上的情況使用UIView的動(dòng)畫封裝方法都可以搞定.
3.1 基礎(chǔ)動(dòng)畫
原理講過(guò)直接上封裝的方法:
//方法1:block方式
/*開(kāi)始動(dòng)畫,UIView的動(dòng)畫方法執(zhí)行完后動(dòng)畫會(huì)停留在重點(diǎn)位置,而不需要進(jìn)行任何特殊處理
duration:執(zhí)行時(shí)間
delay:延遲時(shí)間
options:動(dòng)畫設(shè)置,例如自動(dòng)恢復(fù)、勻速運(yùn)動(dòng)等
completion:動(dòng)畫完成回調(diào)方法
*/
// [UIView animateWithDuration:5.0 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
// _imageView.center=location;
// } completion:^(BOOL finished) {
// NSLog(@"Animation end.");
// }];
//方法2:靜態(tài)方法
//開(kāi)始動(dòng)畫
[UIView beginAnimations:@"KCBasicAnimation" context:nil];
[UIView setAnimationDuration:5.0];
//[UIView setAnimationDelay:1.0];//設(shè)置延遲
//[UIView setAnimationRepeatAutoreverses:NO];//是否回復(fù)
//[UIView setAnimationRepeatCount:10];//重復(fù)次數(shù)
//[UIView setAnimationStartDate:(NSDate *)];//設(shè)置動(dòng)畫開(kāi)始運(yùn)行的時(shí)間
//[UIView setAnimationDelegate:self];//設(shè)置代理
//[UIView setAnimationWillStartSelector:(SEL)];//設(shè)置動(dòng)畫開(kāi)始運(yùn)動(dòng)的執(zhí)行方法
//[UIView setAnimationDidStopSelector:(SEL)];//設(shè)置動(dòng)畫運(yùn)行結(jié)束后的執(zhí)行方法
_imageView.center=location;
//提交動(dòng)畫
[UIView commitAnimations];
擴(kuò)展:彈簧動(dòng)畫效果.
由于在iOS開(kāi)發(fā)中彈性動(dòng)畫使用很普遍,所以在iOS7蘋果官方直接提供了一個(gè)方法用于彈性動(dòng)畫開(kāi)發(fā).
/*創(chuàng)建彈性動(dòng)畫 - block
damping:阻尼,范圍0-1,阻尼越接近于0,彈性效果越明顯
velocity:彈性復(fù)位的速度
*/
[UIView animateWithDuration:5.0 delay:0 usingSpringWithDamping:0.1 initialSpringVelocity:1.0 options:UIViewAnimationOptionCurveLinear animations:^{
_imageView.center = location; //CGPointMake(160, 284);
} completion:nil];
補(bǔ)充:options-動(dòng)畫參數(shù)
在動(dòng)畫方法中有一個(gè)option參數(shù),UIViewAnimationOptions類型,它是一個(gè)枚舉類型,動(dòng)畫參數(shù)分為三類,可以組合使用:
- 常規(guī)動(dòng)畫屬性設(shè)置(可以同時(shí)選擇多個(gè)進(jìn)行設(shè)置)
UIViewAnimationOptionLayoutSubviews:動(dòng)畫過(guò)程中保證子視圖跟隨運(yùn)動(dòng)。
UIViewAnimationOptionAllowUserInteraction:動(dòng)畫過(guò)程中允許用戶交互。
UIViewAnimationOptionBeginFromCurrentState:所有視圖從當(dāng)前狀態(tài)開(kāi)始運(yùn)行。
UIViewAnimationOptionRepeat:重復(fù)運(yùn)行動(dòng)畫。
UIViewAnimationOptionAutoreverse :動(dòng)畫運(yùn)行到結(jié)束點(diǎn)后仍然以動(dòng)畫方式回到初始點(diǎn)。
UIViewAnimationOptionOverrideInheritedDuration:忽略嵌套動(dòng)畫時(shí)間設(shè)置。
UIViewAnimationOptionOverrideInheritedCurve:忽略嵌套動(dòng)畫速度設(shè)置。關(guān)鍵幀動(dòng)畫沒(méi)有
UIViewAnimationOptionAllowAnimatedContent:動(dòng)畫過(guò)程中重繪視圖(注意僅僅適用于轉(zhuǎn)場(chǎng)動(dòng)畫)。
UIViewAnimationOptionShowHideTransitionViews:視圖切換時(shí)直接隱藏舊視圖、顯示新視圖,而不是將舊視圖從父視圖移除(僅僅適用于轉(zhuǎn)場(chǎng)動(dòng)畫)
UIViewAnimationOptionOverrideInheritedOptions :不繼承父動(dòng)畫設(shè)置或動(dòng)畫類型。
- 動(dòng)畫速度控制(可從其中選擇一個(gè)設(shè)置)
UIViewAnimationOptionCurveEaseInOut:動(dòng)畫先緩慢,然后逐漸加速。
UIViewAnimationOptionCurveEaseIn :動(dòng)畫逐漸變慢。
UIViewAnimationOptionCurveEaseOut:動(dòng)畫逐漸加速。
UIViewAnimationOptionCurveLinear :動(dòng)畫勻速執(zhí)行,默認(rèn)值。
- 轉(zhuǎn)場(chǎng)類型(僅適用于轉(zhuǎn)場(chǎng)動(dòng)畫設(shè)置,可以從中選擇一個(gè)進(jìn)行設(shè)置,基本動(dòng)畫、關(guān)鍵幀動(dòng)畫不需要設(shè)置)
UIViewAnimationOptionTransitionNone:沒(méi)有轉(zhuǎn)場(chǎng)動(dòng)畫效果。
UIViewAnimationOptionTransitionFlipFromLeft :從左側(cè)翻轉(zhuǎn)效果。
UIViewAnimationOptionTransitionFlipFromRight:從右側(cè)翻轉(zhuǎn)效果。
UIViewAnimationOptionTransitionCurlUp:向后翻頁(yè)的動(dòng)畫過(guò)渡效果。
UIViewAnimationOptionTransitionCurlDown :向前翻頁(yè)的動(dòng)畫過(guò)渡效果。
UIViewAnimationOptionTransitionCrossDissolve:舊視圖溶解消失顯示下一個(gè)新視圖的效果。
UIViewAnimationOptionTransitionFlipFromTop :從上方翻轉(zhuǎn)效果。
UIViewAnimationOptionTransitionFlipFromBottom:從底部翻轉(zhuǎn)效果。
3.2 關(guān)鍵幀動(dòng)畫
從iOS7開(kāi)始UIView動(dòng)畫中封裝了關(guān)鍵幀動(dòng)畫
/*關(guān)鍵幀動(dòng)畫
options:
*/
[UIView animateKeyframesWithDuration:5.0 delay:0 options: UIViewAnimationOptionCurveLinear| UIViewAnimationOptionCurveLinear animations:^{
//第二個(gè)關(guān)鍵幀(準(zhǔn)確的說(shuō)第一個(gè)關(guān)鍵幀是開(kāi)始位置):從0秒開(kāi)始持續(xù)50%的時(shí)間,也就是5.0*0.5=2.5秒
[UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:0.5 animations:^{
_imageView.center=CGPointMake(80.0, 220.0);
}];
//第三個(gè)關(guān)鍵幀,從0.5*5.0秒開(kāi)始,持續(xù)5.0*0.25=1.25秒
[UIView addKeyframeWithRelativeStartTime:0.5 relativeDuration:0.25 animations:^{
_imageView.center=CGPointMake(45.0, 300.0);
}];
//第四個(gè)關(guān)鍵幀:從0.75*5.0秒開(kāi)始,持所需5.0*0.25=1.25秒
[UIView addKeyframeWithRelativeStartTime:0.75 relativeDuration:0.25 animations:^{
_imageView.center=CGPointMake(55.0, 400.0);
}];
} completion:^(BOOL finished) {
NSLog(@"Animation end.");
}];
關(guān)鍵幀的options,絕大多數(shù)同上,另有動(dòng)畫模式設(shè)置.
動(dòng)畫模式設(shè)置(同前面關(guān)鍵幀動(dòng)畫動(dòng)畫模式一一對(duì)應(yīng),可以從其中選擇一個(gè)進(jìn)行設(shè)置)
UIViewKeyframeAnimationOptionCalculationModeLinear:連續(xù)運(yùn)算模式。
UIViewKeyframeAnimationOptionCalculationModeDiscrete :離散運(yùn)算模式。
UIViewKeyframeAnimationOptionCalculationModePaced:均勻執(zhí)行運(yùn)算模式。
UIViewKeyframeAnimationOptionCalculationModeCubic:平滑運(yùn)算模式。
UIViewKeyframeAnimationOptionCalculationModeCubicPaced:平滑均勻運(yùn)算模式。
注意:前面說(shuō)過(guò)關(guān)鍵幀動(dòng)畫有兩種形式,上面演示的是屬性值關(guān)鍵幀動(dòng)畫,路徑關(guān)鍵幀動(dòng)畫目前UIView還不支持。
3.3 轉(zhuǎn)場(chǎng)動(dòng)畫
從iOS4.0開(kāi)始,UIView直接封裝了轉(zhuǎn)場(chǎng)動(dòng)畫.
[UIView transitionWithView:_imageView duration:1.0 options:option animations:^{
_imageView.image=[self getImage:isNext];
} completion:nil];
#pragma mark 取得當(dāng)前圖片
-(UIImage *)getImage:(BOOL)isNext{
if (isNext) {
_currentIndex=(_currentIndex+1)%IMAGE_COUNT;
}else{
_currentIndex=(_currentIndex-1+IMAGE_COUNT)%IMAGE_COUNT;
}
NSString *imageName=[NSString stringWithFormat:@"%i.jpg",_currentIndex];
return [UIImage imageNamed:imageName];
}
上面的轉(zhuǎn)場(chǎng)動(dòng)畫演示中,其實(shí)僅僅有一個(gè)視圖UIImageView做轉(zhuǎn)場(chǎng)動(dòng)畫,每次轉(zhuǎn)場(chǎng)通過(guò)切換UIImageView的內(nèi)容而已。如果有兩個(gè)完全不同的視圖,并且每個(gè)視圖布局都很復(fù)雜,此時(shí)要在這兩個(gè)視圖之間進(jìn)行轉(zhuǎn)場(chǎng)可以使用+ (void)transitionFromView:(UIView *)fromView toView:(UIView *)toView duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options completion:(void (^)(BOOL finished))completion NS_AVAILABLE_IOS(4_0)方法進(jìn)行兩個(gè)視圖間的轉(zhuǎn)場(chǎng),需要注意的是默認(rèn)情況下轉(zhuǎn)出的視圖會(huì)從父視圖移除,轉(zhuǎn)入后重新添加,可以通過(guò) UIViewAnimationOptionShowHideTransitionViews參數(shù)設(shè)置,設(shè)置此參數(shù)后轉(zhuǎn)出的視圖會(huì)隱藏(不會(huì)移除)轉(zhuǎn)入后再顯示。
注意:轉(zhuǎn)場(chǎng)動(dòng)畫設(shè)置參數(shù)完全同基本動(dòng)畫參數(shù)設(shè)置;同直接使用轉(zhuǎn)場(chǎng)動(dòng)畫不同的是使用UIView的裝飾方法進(jìn)行轉(zhuǎn)場(chǎng)動(dòng)畫其動(dòng)畫效果較少,因?yàn)檫@里無(wú)法直接使用私有API。