目錄
- 一 Core Animation
- 二 核心動(dòng)畫(huà)
- 2.1 基礎(chǔ)動(dòng)畫(huà)
- 2.2 關(guān)鍵幀動(dòng)畫(huà)
- 2.3 動(dòng)畫(huà)組
- 2.4 轉(zhuǎn)場(chǎng)動(dòng)畫(huà)
- 2.5 逐幀動(dòng)畫(huà)
- 三 UIView動(dòng)畫(huà)封裝
- 3.1 基礎(chǔ)動(dòng)畫(huà)
- 3.2 彈簧動(dòng)畫(huà)
- 3.3 關(guān)鍵幀動(dòng)畫(huà)
- 3.4 轉(zhuǎn)場(chǎng)動(dòng)畫(huà)
一 Core Animation
大家都知道在iOS中實(shí)現(xiàn)一個(gè)動(dòng)畫(huà)相當(dāng)簡(jiǎn)單,只要調(diào)用UIView的塊代碼即可實(shí)現(xiàn)一個(gè)動(dòng)畫(huà)效果,這在其他系統(tǒng)開(kāi)發(fā)中基本不可能實(shí)現(xiàn)。下面通過(guò)一個(gè)簡(jiǎn)單的UIView進(jìn)行一個(gè)圖片放大動(dòng)畫(huà)效果演示:
- (void)addAnimation {
UIImage *image=[UIImage imageNamed:@"girl"];
UIImageView *imageView=[[UIImageView alloc]init];
imageView.image=image;
imageView.frame=CGRectMake(120, 140, 80, 80);
imageView.center = self.view.center;
[self.view addSubview:imageView];
//兩秒后開(kāi)始一個(gè)持續(xù)一分鐘的動(dòng)畫(huà)
[UIView animateWithDuration:1 delay:2 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
imageView.frame=CGRectMake(80, 100, 160, 160);
} completion:nil];
}

使用上面UIView封裝的方法進(jìn)行動(dòng)畫(huà)設(shè)置固然十分方便,但是具體動(dòng)畫(huà)如何實(shí)現(xiàn)我們是不清楚的,而且上面的代碼還有一些問(wèn)題是無(wú)法解決的,例如:如何控制動(dòng)畫(huà)的暫停?如何進(jìn)行動(dòng)畫(huà)的組合?
這里就需要了解iOS的核心動(dòng)畫(huà)Core Animation(包含在Quartz Core框架中)
二 核心動(dòng)畫(huà)
在iOS中核心動(dòng)畫(huà)分為幾類:
- 基礎(chǔ)動(dòng)畫(huà)
- 關(guān)鍵幀動(dòng)畫(huà)
- 動(dòng)畫(huà)組
- 轉(zhuǎn)場(chǎng)動(dòng)畫(huà)
- 逐幀動(dòng)畫(huà)
各個(gè)類的關(guān)系大致如下:
-
CAAnimation:核心動(dòng)畫(huà)的基礎(chǔ)類,不能直接使用,負(fù)責(zé)動(dòng)畫(huà)運(yùn)行時(shí)間、速度的控制,本身實(shí)現(xiàn)了CAMediaTiming協(xié)議。 -
CAPropertyAnimation:屬性動(dòng)畫(huà)的基類(通過(guò)屬性進(jìn)行動(dòng)畫(huà)設(shè)置,注意是可動(dòng)畫(huà)屬性),不能直接使用。 -
CAAnimationGroup:動(dòng)畫(huà)組,動(dòng)畫(huà)組是一種組合模式設(shè)計(jì),可以通過(guò)組合動(dòng)畫(huà)組來(lái)進(jìn)行所有動(dòng)畫(huà)行為的統(tǒng)一控制,組中的所有動(dòng)畫(huà)效果可以并發(fā)執(zhí)行。 -
CATransition:轉(zhuǎn)場(chǎng)動(dòng)畫(huà),通過(guò)濾鏡進(jìn)行動(dòng)畫(huà)效果設(shè)置。 -
CABasicAnimation:基礎(chǔ)動(dòng)畫(huà),通過(guò)屬性修改進(jìn)行動(dòng)畫(huà)參數(shù)控制,只有初始狀態(tài)和結(jié)束狀態(tài)。 -
CAKeyframeAnimation:關(guān)鍵幀動(dòng)畫(huà),同樣是通過(guò)屬性進(jìn)行動(dòng)畫(huà)參數(shù)控制,但是同基礎(chǔ)動(dòng)畫(huà)不同的是它可以有多個(gè)狀態(tài)控制。
基礎(chǔ)動(dòng)畫(huà)、關(guān)鍵幀動(dòng)畫(huà)都屬于屬性動(dòng)畫(huà),就是通過(guò)修改屬性值產(chǎn)生動(dòng)畫(huà)效果,開(kāi)發(fā)人員只需要設(shè)置初始值和結(jié)束值,中間的過(guò)程動(dòng)畫(huà)(又叫“補(bǔ)間動(dòng)畫(huà)”)由系統(tǒng)自動(dòng)計(jì)算產(chǎn)生。和基礎(chǔ)動(dòng)畫(huà)不同的是關(guān)鍵幀動(dòng)畫(huà)可以設(shè)置多個(gè)屬性值,每?jī)蓚€(gè)屬性中間的補(bǔ)間動(dòng)畫(huà)由系統(tǒng)自動(dòng)完成,因此從這個(gè)角度而言基礎(chǔ)動(dòng)畫(huà)又可以看成是有兩個(gè)關(guān)鍵幀的關(guān)鍵幀動(dòng)畫(huà)。
2.1 基礎(chǔ)動(dòng)畫(huà)
在開(kāi)發(fā)過(guò)程中很多情況下通過(guò)基礎(chǔ)動(dòng)畫(huà)就可以滿足開(kāi)發(fā)需求,如果不使用UIView封裝的方法,動(dòng)畫(huà)創(chuàng)建一般分為以下幾步:
- 1.初始化動(dòng)畫(huà)并設(shè)置動(dòng)畫(huà)屬性
- 2.設(shè)置動(dòng)畫(huà)屬性初始值(可以省略)、結(jié)束值以及其他動(dòng)畫(huà)屬性
- 3.給圖層添加動(dòng)畫(huà)
下面以一個(gè)移動(dòng)動(dòng)畫(huà)為例進(jìn)行演示,在這個(gè)例子中點(diǎn)擊屏幕哪個(gè)位置落花將飛向哪里

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.title = @"基礎(chǔ)動(dòng)畫(huà)";
[self addSubLayer];
}
// 添加一個(gè)圖層
- (void)addSubLayer {
//設(shè)置背景(注意這個(gè)圖片其實(shí)在根圖層)
UIImage *backgroundImage = [UIImage imageNamed:@"women"];
self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];
// 自定義一個(gè)圖層
_layer=[[CALayer alloc]init];
_layer.bounds=CGRectMake(0, 0, 35, 35);
_layer.position=CGPointMake(50, 150);
_layer.contents=(id)[UIImage imageNamed:@"girl"].CGImage;
[self.view.layer addSublayer:_layer];
}
#pragma mark 移動(dòng)動(dòng)畫(huà)
-(void)translatonAnimation:(CGPoint)location{
//1.創(chuàng)建動(dòng)畫(huà)并指定動(dòng)畫(huà)屬性
CABasicAnimation *basicAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
//2.設(shè)置動(dòng)畫(huà)屬性初始值和結(jié)束值
//basicAnimation.fromValue=[NSNumber numberWithInteger:50];//可以不設(shè)置,默認(rèn)為圖層初始狀態(tài)
basicAnimation.toValue = [NSValue valueWithCGPoint:location];
//設(shè)置其他動(dòng)畫(huà)屬性
basicAnimation.duration = 2.0;//動(dòng)畫(huà)時(shí)間5秒
// basicAnimation.repeatCount=HUGE_VALF;//設(shè)置重復(fù)次數(shù),HUGE_VALF可看做無(wú)窮大,起到循環(huán)動(dòng)畫(huà)的效果
basicAnimation.removedOnCompletion = NO;//運(yùn)行一次是否移除動(dòng)畫(huà)
//3.添加動(dòng)畫(huà)到圖層,注意key相當(dāng)于給動(dòng)畫(huà)進(jìn)行命名,以后獲得該動(dòng)畫(huà)時(shí)可以使用此名稱獲取
[_layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Translation"];
}
// 指哪飛哪
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
UITouch *touch = touches.anyObject;
CGPoint point = [touch locationInView:self.view];
[self translatonAnimation:point];
}
前面說(shuō)過(guò)圖層動(dòng)畫(huà)的本質(zhì)就是將圖層內(nèi)部的內(nèi)容轉(zhuǎn)化為位圖經(jīng)硬件操作形成一種動(dòng)畫(huà)效果,其實(shí)圖層本身并沒(méi)有任何的變化。上面的動(dòng)畫(huà)中圖層并沒(méi)有因?yàn)閯?dòng)畫(huà)效果而改變它的位置(對(duì)于縮放動(dòng)畫(huà)其大小也是不會(huì)改變的),所以動(dòng)畫(huà)完成之后圖層還是在原來(lái)的顯示位置沒(méi)有任何變化,如果這個(gè)圖層在一個(gè)UIView中你會(huì)發(fā)現(xiàn)在UIView移動(dòng)過(guò)程中你要觸發(fā)UIView的點(diǎn)擊事件也只能點(diǎn)擊原來(lái)的位置(即使它已經(jīng)運(yùn)動(dòng)到了別的位置),因?yàn)樗奈恢脧膩?lái)沒(méi)有變過(guò)。當(dāng)然解決這個(gè)問(wèn)題方法比較多,這里不妨在動(dòng)畫(huà)完成之后重新設(shè)置它的位置。
如過(guò)給動(dòng)畫(huà)設(shè)置一個(gè)代理去監(jiān)聽(tīng)動(dòng)畫(huà)的開(kāi)始和結(jié)束事件,在動(dòng)畫(huà)開(kāi)始前給動(dòng)畫(huà)添加一個(gè)自定義屬性KCBasicAnimationLocation存儲(chǔ)動(dòng)畫(huà)終點(diǎn)位置,然后在動(dòng)畫(huà)結(jié)束后設(shè)置動(dòng)畫(huà)的位置為終點(diǎn)位置。
如果運(yùn)行上面的代碼大家可能會(huì)發(fā)現(xiàn)另外一個(gè)問(wèn)題,那就是動(dòng)畫(huà)運(yùn)行完成后會(huì)重新從起始點(diǎn)運(yùn)動(dòng)到終點(diǎn)。這個(gè)問(wèn)題產(chǎn)生的原因就是前面提到的,對(duì)于非根圖層,設(shè)置圖層的可動(dòng)畫(huà)屬性(在動(dòng)畫(huà)結(jié)束后重新設(shè)置了position,而position是可動(dòng)畫(huà)屬性)會(huì)產(chǎn)生動(dòng)畫(huà)效果。解決這個(gè)問(wèn)題有兩種辦法:關(guān)閉圖層隱式動(dòng)畫(huà)、設(shè)置動(dòng)畫(huà)圖層為根圖層。顯然這里不能采取后者,因?yàn)楦鶊D層當(dāng)前已經(jīng)作為動(dòng)畫(huà)的背景。
法一
// 設(shè)置代理
basicAnimation.delegate = self;
//存儲(chǔ)當(dāng)前位置在動(dòng)畫(huà)結(jié)束后使用
[basicAnimation setValue:[NSValue valueWithCGPoint:location] forKey:@"KCBasicAnimationLocation"];
#pragma mark - 動(dòng)畫(huà)代理方法
// 動(dòng)畫(huà)開(kāi)始
-(void)animationDidStart:(CAAnimation *)anim{
NSLog(@"animation(%@) start.\r_layer.frame=%@",anim,NSStringFromCGRect(_layer.frame));
NSLog(@"%@",[_layer animationForKey:@"KCBasicAnimation_Translation"]);//通過(guò)前面的設(shè)置的key獲得動(dòng)畫(huà)
}
// 動(dòng)畫(huà)結(jié)束
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
NSLog(@"animation(%@) stop.\r_layer.frame=%@",anim,NSStringFromCGRect(_layer.frame));
_layer.position = [[anim valueForKey:@"KCBasicAnimationLocation"] CGPointValue];
}
發(fā)現(xiàn)動(dòng)畫(huà)完畢后,馬上回到起點(diǎn)然后直接跳轉(zhuǎn)到終點(diǎn)。
法二
要關(guān)閉隱式動(dòng)畫(huà)需要用到動(dòng)畫(huà)事務(wù)CATransaction,在事務(wù)內(nèi)將隱式動(dòng)畫(huà)關(guān)閉。
// 動(dòng)畫(huà)結(jié)束
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
NSLog(@"animation(%@) stop.\r_layer.frame=%@",anim,NSStringFromCGRect(_layer.frame));
//開(kāi)啟事務(wù)
[CATransaction begin];
//禁用隱式動(dòng)畫(huà)
[CATransaction setDisableActions:YES];
_layer.position=[[anim valueForKey:@"KCBasicAnimationLocation"] CGPointValue];
//提交事務(wù)
[CATransaction commit];
}
動(dòng)畫(huà)完畢后停留在終點(diǎn)
上面通過(guò)在animationDidStop中重新設(shè)置動(dòng)畫(huà)的位置主要為了說(shuō)明隱式動(dòng)畫(huà)關(guān)閉和動(dòng)畫(huà)事件之間傳參的內(nèi)容,發(fā)現(xiàn)這種方式有可能在動(dòng)畫(huà)運(yùn)行完之后出現(xiàn)從原點(diǎn)瞬間回到終點(diǎn)的過(guò)程,最早在調(diào)試的時(shí)候沒(méi)有發(fā)現(xiàn)這個(gè)問(wèn)題,其實(shí)解決這個(gè)問(wèn)題并不難,首先必須設(shè)置fromValue,其次在動(dòng)畫(huà)開(kāi)始前設(shè)置動(dòng)畫(huà)position為終點(diǎn)位置(當(dāng)然也必須關(guān)閉隱式動(dòng)畫(huà))。但是這里主要還是出于學(xué)習(xí)的目的,真正開(kāi)發(fā)的時(shí)候做平移動(dòng)畫(huà)直接使用隱式動(dòng)畫(huà)即可,沒(méi)有必要那么麻煩。
- 圖層的形變都是基于錨點(diǎn)進(jìn)行的。例如旋轉(zhuǎn),旋轉(zhuǎn)的中心點(diǎn)就是圖層的錨點(diǎn)。
添加旋轉(zhuǎn)動(dòng)畫(huà)
#pragma mark 旋轉(zhuǎn)動(dòng)畫(huà)
- (void)rotationAnimation{
//1.創(chuàng)建動(dòng)畫(huà)并指定動(dòng)畫(huà)屬性
CABasicAnimation *basicAnimation=[CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
//2.設(shè)置動(dòng)畫(huà)屬性初始值、結(jié)束值
// basicAnimation.fromValue=[NSNumber numberWithInt:M_PI_2];
basicAnimation.toValue = [NSNumber numberWithFloat:M_PI_2 * 3];
//設(shè)置其他動(dòng)畫(huà)屬性
basicAnimation.duration = 2.0;
basicAnimation.autoreverses = true;//旋轉(zhuǎn)后再旋轉(zhuǎn)到原來(lái)的位置
//4.添加動(dòng)畫(huà)到圖層,注意key相當(dāng)于給動(dòng)畫(huà)進(jìn)行命名,以后獲得該動(dòng)畫(huà)時(shí)可以使用此名稱獲取
[_layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Rotation"];
}
上面代碼中結(jié)合兩種動(dòng)畫(huà)操作,需要注意的是只給移動(dòng)動(dòng)畫(huà)設(shè)置了代理,在旋轉(zhuǎn)動(dòng)畫(huà)中并沒(méi)有設(shè)置代理,否則代理方法會(huì)執(zhí)行兩遍。由于旋轉(zhuǎn)動(dòng)畫(huà)會(huì)無(wú)限循環(huán)執(zhí)行(上面設(shè)置了重復(fù)次數(shù)無(wú)窮大),并且兩個(gè)動(dòng)畫(huà)的執(zhí)行時(shí)間沒(méi)有必然的關(guān)系,這樣一來(lái)移動(dòng)停止后可能還在旋轉(zhuǎn),為了讓移動(dòng)動(dòng)畫(huà)停止后旋轉(zhuǎn)動(dòng)畫(huà),停止就需要使用到動(dòng)畫(huà)的暫停和恢復(fù)方法。
下面的代碼演示了移動(dòng)動(dòng)畫(huà)結(jié)束后旋轉(zhuǎn)動(dòng)畫(huà)暫停,并且當(dāng)再次點(diǎn)擊動(dòng)畫(huà)時(shí)旋轉(zhuǎn)恢復(fù)的過(guò)程(注意在移動(dòng)過(guò)程中如果再次點(diǎn)擊屏幕可以暫停移動(dòng)和旋轉(zhuǎn)動(dòng)畫(huà),再次點(diǎn)擊可以恢復(fù)兩種動(dòng)畫(huà)。但是當(dāng)移動(dòng)結(jié)束后觸發(fā)了移動(dòng)動(dòng)畫(huà)的完成事件如果再次點(diǎn)擊屏幕則只能恢復(fù)旋轉(zhuǎn)動(dòng)畫(huà),因?yàn)榇藭r(shí)移動(dòng)動(dòng)畫(huà)已經(jīng)結(jié)束而不是暫停,無(wú)法再恢復(fù))。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
UITouch *touch = touches.anyObject;
CGPoint position = [touch locationInView:self.view];
CAAnimation *animation = [_layer animationForKey:@"ZXBasicAnimation_rotation"];
//創(chuàng)建并開(kāi)始動(dòng)畫(huà)
if (animation) {
if (_layer.speed == 0) {
[self animationResume];
}else{
[self animationPause];
}
} else {
[self translatonAnimation:position];
[self rotationAnimation];
}
}
#pragma mark - 暫停 | 繼續(xù)
- (void)animationPause {
//取得指定圖層動(dòng)畫(huà)的媒體時(shí)間,后面參數(shù)用于指定子圖層,這里不需要
CFTimeInterval interval = [_layer convertTime:CACurrentMediaTime() fromLayer:nil];
//設(shè)置時(shí)間偏移量,保證暫停時(shí)停留在旋轉(zhuǎn)的位置
_layer.timeOffset = interval;
//速度設(shè)置為零,暫停動(dòng)畫(huà)
_layer.speed = 0;
}
- (void)animationResume {
//獲取暫停的時(shí)間
CFTimeInterval beginTime = CACurrentMediaTime() - _layer.timeOffset;
//設(shè)置偏移量
_layer.timeOffset = 0;
//設(shè)置開(kāi)始時(shí)間
_layer.beginTime = beginTime;
//設(shè)置動(dòng)畫(huà)速度,開(kāi)始運(yùn)動(dòng)
_layer.speed = 1.0;
}

注意: 動(dòng)畫(huà)暫停針對(duì)的是圖層而不是圖層中的某個(gè)動(dòng)畫(huà)。
要做無(wú)限循環(huán)的動(dòng)畫(huà),動(dòng)畫(huà)的removedOnCompletion屬性必須設(shè)置為NO,否則運(yùn)行一次動(dòng)畫(huà)就會(huì)銷毀。
2.2 關(guān)鍵幀動(dòng)畫(huà)
熟悉flash開(kāi)發(fā)的朋友對(duì)于關(guān)鍵幀動(dòng)畫(huà)應(yīng)該不陌生,這種動(dòng)畫(huà)方式在flash開(kāi)發(fā)中經(jīng)常用到。關(guān)鍵幀動(dòng)畫(huà)就是在動(dòng)畫(huà)控制過(guò)程中開(kāi)發(fā)者指定主要的動(dòng)畫(huà)狀態(tài),至于各個(gè)狀態(tài)間動(dòng)畫(huà)如何進(jìn)行則由系統(tǒng)自動(dòng)運(yùn)算補(bǔ)充(每?jī)蓚€(gè)關(guān)鍵幀之間系統(tǒng)形成的動(dòng)畫(huà)稱為“補(bǔ)間動(dòng)畫(huà)”),這種動(dòng)畫(huà)的好處就是開(kāi)發(fā)者不用逐個(gè)控制每個(gè)動(dòng)畫(huà)幀,而只要關(guān)心幾個(gè)關(guān)鍵幀的狀態(tài)即可。
關(guān)鍵幀動(dòng)畫(huà)開(kāi)發(fā)分為兩種形式:
- 一種是通過(guò)設(shè)置不同的屬性值進(jìn)行關(guān)鍵幀控制,
- 另一種是通過(guò)繪制路徑進(jìn)行關(guān)鍵幀控制。
后者優(yōu)先級(jí)高于前者,如果設(shè)置了路徑則屬性值就不再起作用。
在這里需要設(shè)置四個(gè)關(guān)鍵幀(如圖中四個(gè)關(guān)鍵點(diǎn)),具體代碼如下(動(dòng)畫(huà)創(chuàng)建過(guò)程同基本動(dòng)畫(huà)基本完全一致):
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.title = @"關(guān)鍵幀動(dòng)畫(huà)";
//設(shè)置背景(注意這個(gè)圖片其實(shí)在根圖層)
UIImage* backgroundImage = [UIImage imageNamed:@"snow"];
self.view.backgroundColor = [UIColor colorWithPatternImage:backgroundImage];
//自定義一個(gè)圖層
self.layer = [[CALayer alloc]init];
self.layer.bounds= CGRectMake(0, 0, 10, 20);
self.layer.position = CGPointMake(50, 150);
self.layer.contents = (id)[UIImage imageNamed:@"snowflake"].CGImage;
[self.view.layer addSublayer:self.layer];
//創(chuàng)建動(dòng)畫(huà)
[self translationAnimation_values];
}
- (void)translationAnimation_values {
//1.創(chuàng)建關(guān)鍵幀動(dòng)畫(huà)并設(shè)置動(dòng)畫(huà)屬性
CAKeyframeAnimation* keyFrameAnimation =[CAKeyframeAnimation animationWithKeyPath:@"position"];
//2.設(shè)置關(guān)鍵幀,這里有四個(gè)關(guān)鍵幀
NSValue* key1 = [NSValue valueWithCGPoint:self.layer.position];//對(duì)于關(guān)鍵幀動(dòng)畫(huà)初始值不能省略
NSValue* key2 = [NSValue valueWithCGPoint:CGPointMake(80, 220)];
NSValue* key3 = [NSValue valueWithCGPoint:CGPointMake(45, 320)];
NSValue* key4 = [NSValue valueWithCGPoint:CGPointMake(75, 420)];
//設(shè)置其他屬性
keyFrameAnimation.values = @[key1,key2,key3,key4];
keyFrameAnimation.duration = 7;
//keyFrameAnimation.beginTime = CACurrentMediaTime() + 2;//設(shè)置延遲2秒執(zhí)行
keyFrameAnimation.keyTimes = @[@(2/7.0),@(5.5/7),@(6.25/7),@1.0];
//3.添加動(dòng)畫(huà)到圖層,添加動(dòng)畫(huà)后就會(huì)行動(dòng)畫(huà)
[self.layer addAnimation:keyFrameAnimation forKey:@"myAnimation"];
}

上面的方式固然比前面使用基礎(chǔ)動(dòng)畫(huà)效果要好一些,但其實(shí)還是存在問(wèn)題,那就是落花飛落的路徑是直線的,當(dāng)然這個(gè)直線是根據(jù)程序中設(shè)置的四個(gè)關(guān)鍵幀自動(dòng)形成的,那么如何讓它沿著曲線飄落呢?這就是第二種類型的關(guān)鍵幀動(dòng)畫(huà),通過(guò)描繪路徑進(jìn)行關(guān)鍵幀動(dòng)畫(huà)控制。假設(shè)讓落花沿著下面的曲線路徑飄落:
- 這是一條貝塞爾曲線
- (void)translationAnimation_path {
//1.創(chuàng)建關(guān)鍵幀動(dòng)畫(huà)并設(shè)置動(dòng)畫(huà)屬性
CAKeyframeAnimation* keyFrameAnimation =[CAKeyframeAnimation animationWithKeyPath:@"position"];
//2.設(shè)置路徑
//貝塞爾曲線
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, self.layer.position.x, self.layer.position.y);
CGPathAddCurveToPoint(path, NULL, 160, 280, -30, 300, 55, 400);//繪制二次貝塞爾曲線
keyFrameAnimation.path = path;
CGPathRelease(path);
keyFrameAnimation.duration = 5.0;
// keyFrameAnimation.beginTime = CACurrentMediaTime() + 2;//設(shè)置延遲2秒執(zhí)行
//3.添加動(dòng)畫(huà)到圖層,添加動(dòng)畫(huà)后就會(huì)行動(dòng)畫(huà)
[self.layer addAnimation:keyFrameAnimation forKey:@"myAnimation"];
}

但是這里需要注意,對(duì)于路徑類型的關(guān)鍵幀動(dòng)畫(huà)系統(tǒng)是從描繪路徑的位置開(kāi)始路徑,直到路徑結(jié)束。如果上面的路徑不是貝塞爾曲線而是矩形路徑那么它會(huì)從矩形的左上角開(kāi)始運(yùn)行,順時(shí)針一周回到左上角;如果指定的路徑是一個(gè)橢圓,那么動(dòng)畫(huà)運(yùn)行的路徑是從橢圓右側(cè)開(kāi)始(0度)順時(shí)針一周回到右側(cè)。
-
補(bǔ)充:其他屬性
在關(guān)鍵幀動(dòng)畫(huà)中還有一些動(dòng)畫(huà)屬性初學(xué)者往往比較容易混淆,這里專門(mén)針對(duì)這些屬性做一下介紹。 -
keyTimes:各個(gè)關(guān)鍵幀的時(shí)間控制。前面使用values設(shè)置了四個(gè)關(guān)鍵幀,默認(rèn)情況下每?jī)蓭g的間隔為:8/(4-1)秒。如果想要控制動(dòng)畫(huà)從第一幀到第二針占用時(shí)間4秒,從第二幀到第三幀時(shí)間為2秒,而從第三幀到第四幀時(shí)間2秒的話,就可以通過(guò)keyTimes進(jìn)行設(shè)置。keyTimes中存儲(chǔ)的是時(shí)間占用比例點(diǎn),此時(shí)可以設(shè)置keyTimes的值為0.0,0.5,0.75,1.0(當(dāng)然必須轉(zhuǎn)換為NSNumber),也就是說(shuō)1到2幀運(yùn)行到總時(shí)間的50%,2到3幀運(yùn)行到總時(shí)間的75%,3到4幀運(yùn)行到8秒結(jié)束。 -
caculationMode:動(dòng)畫(huà)計(jì)算模式。還拿上面keyValues動(dòng)畫(huà)舉例,之所以1到2幀能形成連貫性動(dòng)畫(huà)而不是直接從第1幀經(jīng)過(guò)8/3秒到第2幀是因?yàn)閯?dòng)畫(huà)模式是連續(xù)的(值為kCAAnimationLinear,這是計(jì)算模式的默認(rèn)值);而如果指定了動(dòng)畫(huà)模式為kCAAnimationDiscrete離散的那么你會(huì)看到動(dòng)畫(huà)從第1幀經(jīng)過(guò)8/3秒直接到第2幀,中間沒(méi)有任何過(guò)渡。其他動(dòng)畫(huà)模式還有:kCAAnimationPaced(均勻執(zhí)行,會(huì)忽略keyTimes)、kCAAnimationCubic(平滑執(zhí)行,對(duì)于位置變動(dòng)關(guān)鍵幀動(dòng)畫(huà)運(yùn)行軌跡更平滑)、kCAAnimationCubicPaced(平滑均勻執(zhí)行)。
2.3 動(dòng)畫(huà)組
實(shí)際開(kāi)發(fā)中一個(gè)物體的運(yùn)動(dòng)往往是復(fù)合運(yùn)動(dòng),單一屬性的運(yùn)動(dòng)情況比較少,但恰恰屬性動(dòng)畫(huà)每次進(jìn)行動(dòng)畫(huà)設(shè)置時(shí)一次只能設(shè)置一個(gè)屬性進(jìn)行動(dòng)畫(huà)控制(不管是基礎(chǔ)動(dòng)畫(huà)還是關(guān)鍵幀動(dòng)畫(huà)都是如此),這樣一來(lái)要做一個(gè)復(fù)合運(yùn)動(dòng)的動(dòng)畫(huà)就必須創(chuàng)建多個(gè)屬性動(dòng)畫(huà)進(jìn)行組合。對(duì)于一兩種動(dòng)畫(huà)的組合或許處理起來(lái)還比較容易,但是對(duì)于更多動(dòng)畫(huà)的組合控制往往會(huì)變得很麻煩,動(dòng)畫(huà)組的產(chǎn)生就是基于這樣一種情況而產(chǎn)生的。動(dòng)畫(huà)組是一系列動(dòng)畫(huà)的組合,凡是添加到動(dòng)畫(huà)組中的動(dòng)畫(huà)都受控于動(dòng)畫(huà)組,這樣一來(lái)各類動(dòng)畫(huà)公共的行為就可以統(tǒng)一進(jìn)行控制而不必單獨(dú)設(shè)置,而且放到動(dòng)畫(huà)組中的各個(gè)動(dòng)畫(huà)可以并發(fā)執(zhí)行,共同構(gòu)建出復(fù)雜的動(dòng)畫(huà)效果。
前面關(guān)鍵幀動(dòng)畫(huà)部分,路徑動(dòng)畫(huà)看起來(lái)效果雖然很流暢,但是落花本身的旋轉(zhuǎn)運(yùn)動(dòng)沒(méi)有了,這里不妨將基礎(chǔ)動(dòng)畫(huà)部分的旋轉(zhuǎn)動(dòng)畫(huà)和路徑關(guān)鍵幀動(dòng)畫(huà)進(jìn)行組合使得整個(gè)動(dòng)畫(huà)看起來(lái)更加的和諧、順暢。
#pragma mark - 添加基礎(chǔ)旋轉(zhuǎn)動(dòng)畫(huà)
- (CABasicAnimation*)rotationAnimation {
CABasicAnimation* basicAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
CGFloat toValue = M_PI_2*3;
basicAnimation.toValue = [NSNumber numberWithFloat:toValue];
basicAnimation.autoreverses = YES;
basicAnimation.repeatCount = HUGE_VAL;
basicAnimation.removedOnCompletion = NO;
[basicAnimation setValue:[NSNumber numberWithFloat:toValue] forKey:@"ZXBaiscAnimationProperty_toValue"];
return basicAnimation;
}
#pragma mark - 添加關(guān)鍵幀移動(dòng)動(dòng)畫(huà)
- (CAKeyframeAnimation*)translationAnimation {
CAKeyframeAnimation* keyframeAnim = [CAKeyframeAnimation animationWithKeyPath:@"position"];
CGPoint endPoint = CGPointMake(55, 400);
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, self.layer.position.x, self.layer.position.y);
CGPathAddCurveToPoint(path, NULL, 160, 280, -30, 300, endPoint.x, endPoint.y);
keyframeAnim.path = path;
CGPathRelease(path);
[keyframeAnim setValue:[NSValue valueWithCGPoint:endPoint] forKey:@"ZXKeyFrameAnimationProperty_endPosition"];
return keyframeAnim;
}
#pragma mark - 創(chuàng)建動(dòng)畫(huà)組
- (void)groupAnimation {
//1.創(chuàng)建動(dòng)畫(huà)組
CAAnimationGroup* animationGroup = [CAAnimationGroup animation];
//2.設(shè)置組中的動(dòng)畫(huà)和其他屬性
CABasicAnimation* basicAnimation = [self rotationAnimation];
CAKeyframeAnimation* keyFrameAnimation = [self translationAnimation];
animationGroup.animations = @[basicAnimation,keyFrameAnimation];
animationGroup.delegate = self;
animationGroup.duration = 10.0;//設(shè)置動(dòng)畫(huà)時(shí)間,如果動(dòng)畫(huà)組中動(dòng)畫(huà)已經(jīng)設(shè)置過(guò)動(dòng)畫(huà)屬性則不再生效
// animationGroup.beginTime = CACurrentMediaTime() + 5;//延遲5秒執(zhí)行
//3.給圖層添加動(dòng)畫(huà)
[self.layer addAnimation:animationGroup forKey:nil];
}
#pragma mark - CAAnimationDelegate
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
CAAnimationGroup* animationGroup = (CAAnimationGroup*)anim;
CABasicAnimation* basicAnimation = (CABasicAnimation*)animationGroup.animations[0];
CAKeyframeAnimation* keyFrameAnimation = (CAKeyframeAnimation*)animationGroup.animations[1];
CGFloat toValue = [[basicAnimation valueForKey:@"ZXBaiscAnimationProperty_toValue"] floatValue];
CGPoint endPoint = [[keyFrameAnimation valueForKey:@"ZXKeyFrameAnimationProperty_endPosition"] CGPointValue];
[CATransaction begin];
[CATransaction setDisableActions:YES];
//設(shè)置動(dòng)畫(huà)的最終狀態(tài)
self.layer.position = endPoint;
self.layer.transform = CATransform3DMakeRotation(toValue, 0, 0, 1);
[CATransaction commit];
}

2.4 轉(zhuǎn)場(chǎng)動(dòng)畫(huà)
轉(zhuǎn)場(chǎng)動(dòng)畫(huà)就是從一個(gè)場(chǎng)景以動(dòng)畫(huà)的形式過(guò)渡到另一個(gè)場(chǎng)景。轉(zhuǎn)場(chǎng)動(dòng)畫(huà)的使用一般分為以下幾個(gè)步驟:
- 創(chuàng)建轉(zhuǎn)場(chǎng)動(dòng)畫(huà)
- 設(shè)置轉(zhuǎn)場(chǎng)動(dòng)類型、子類型(可選)及其他屬性
- 設(shè)置轉(zhuǎn)場(chǎng)后的新視圖并添加動(dòng)畫(huà)到圖層
這里使用轉(zhuǎn)場(chǎng)動(dòng)畫(huà)利用一個(gè)UIImageView實(shí)現(xiàn)一個(gè)漂亮的無(wú)限循環(huán)圖片瀏覽器。
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.title = @"轉(zhuǎn)場(chǎng)動(dòng)畫(huà)";
self.myImageView = [[UIImageView alloc]initWithFrame:self.view.bounds];
self.myImageView.image = [UIImage imageNamed:@"0"];
self.myImageView.contentMode = UIViewContentModeScaleAspectFit;
[self.view addSubview:_myImageView];
//添加手勢(shì)
UISwipeGestureRecognizer* leftSwipeGesture = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(leftSwipe:)];
leftSwipeGesture.direction = UISwipeGestureRecognizerDirectionLeft;
[self.view addGestureRecognizer:leftSwipeGesture];
UISwipeGestureRecognizer* rightSwipeGesture = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(rightSwipe:)];
rightSwipeGesture.direction = UISwipeGestureRecognizerDirectionRight;
[self.view addGestureRecognizer:rightSwipeGesture];
}
#pragma mark - 向左滑動(dòng)瀏覽下張圖片
- (void)leftSwipe:(UISwipeGestureRecognizer*)gesture {
[self transitionAnimtion:YES];
}
#pragma mark - 向右滑動(dòng)瀏覽上張圖片
- (void)rightSwipe:(UISwipeGestureRecognizer*)gesture {
[self transitionAnimtion:NO];
}
#pragma mark - 添加轉(zhuǎn)場(chǎng)動(dòng)畫(huà)
- (void)transitionAnimtion:(BOOL)flag {
//1.創(chuàng)建轉(zhuǎn)場(chǎng)動(dòng)畫(huà)對(duì)象
CATransition* transition = [[CATransition alloc]init];
//2.設(shè)置動(dòng)畫(huà)類型,注意對(duì)于蘋(píng)果官方?jīng)]有公開(kāi)的動(dòng)畫(huà)類型只能使用字符串,并沒(méi)有對(duì)應(yīng)的常亮使用
transition.type = @"cube";
//設(shè)置子類型
if (flag) {
transition.subtype = kCATransitionFromRight;
} else {
transition.subtype = kCATransitionFromLeft;
}
//設(shè)置動(dòng)畫(huà)時(shí)長(zhǎng)
transition.duration = 1.0f;
//3.設(shè)置轉(zhuǎn)場(chǎng)動(dòng)畫(huà)后,給新視圖添加轉(zhuǎn)場(chǎng)動(dòng)畫(huà)
self.myImageView.image = [self getImage:flag];
[self.myImageView.layer addAnimation:transition forKey:@"abc"];
}
- (UIImage*)getImage:(BOOL)flag {
if (flag) {
self.currentIndex = ++self.currentIndex % kImageCount;
} else {
self.currentIndex = --self.currentIndex % kImageCount;
}
return [UIImage imageNamed:[NSString stringWithFormat:@"%ld",self.currentIndex]];
}

代碼十分簡(jiǎn)單,但是效果和性能卻很驚人。當(dāng)然演示代碼有限,其他動(dòng)畫(huà)類型大家可以自己實(shí)現(xiàn),效果都很絢麗。
2.5逐幀動(dòng)畫(huà)
前面介紹了核心動(dòng)畫(huà)中大部分動(dòng)畫(huà)類型,但是做過(guò)動(dòng)畫(huà)處理的朋友都知道,在動(dòng)畫(huà)制作中還有一種動(dòng)畫(huà)類型“逐幀動(dòng)畫(huà)”。說(shuō)到逐幀動(dòng)畫(huà)相信很多朋友第一個(gè)想到的就是UIImageView,通過(guò)設(shè)置UIImageView的animationImages屬性,然后調(diào)用它的startAnimating方法去播放這組圖片。
當(dāng)然這種方法在某些場(chǎng)景下是可以達(dá)到逐幀的動(dòng)畫(huà)效果,但是它也存在著很大的性能問(wèn)題,并且這種方法一旦設(shè)置完圖片中間的過(guò)程就無(wú)法控制了。當(dāng)然,也許有朋友會(huì)想到利用iOS的定時(shí)器NSTimer定時(shí)更新圖片來(lái)達(dá)到逐幀動(dòng)畫(huà)的效果。這種方式確實(shí)可以解決UIImageView一次性加載大量圖片的問(wèn)題,而且讓播放過(guò)程可控,唯一的缺點(diǎn)就是定時(shí)器方法調(diào)用有時(shí)可能會(huì)因?yàn)楫?dāng)前系統(tǒng)執(zhí)行某種比較占用時(shí)間的任務(wù)造成動(dòng)畫(huà)連續(xù)性出現(xiàn)問(wèn)題。
雖然在核心動(dòng)畫(huà)沒(méi)有直接提供逐幀動(dòng)畫(huà)類型,但是卻提供了用于完成逐幀動(dòng)畫(huà)的相關(guān)對(duì)象CADisplayLink。CADisplayLink是一個(gè)計(jì)時(shí)器,但是同NSTimer不同的是,CADisplayLink的刷新周期同屏幕完全一致。例如在iOS中屏幕刷新周期是60次/秒,CADisplayLink刷新周期同屏幕刷新一致也是60次/秒,這樣一來(lái)使用它完成的逐幀動(dòng)畫(huà)(又稱為“時(shí)鐘動(dòng)畫(huà)”)完全感覺(jué)不到動(dòng)畫(huà)的停滯情況。
iOS程序在運(yùn)行后就進(jìn)入一個(gè)消息循環(huán)中(這個(gè)消息循環(huán)稱為“主運(yùn)行循環(huán)”),整個(gè)程序相當(dāng)于進(jìn)入一個(gè)死循環(huán)中,始終等待用戶輸入。將CADisplayLink加入到主運(yùn)行循環(huán)隊(duì)列后,它的時(shí)鐘周期就和主運(yùn)行循環(huán)保持一致,而主運(yùn)行循環(huán)周期就是屏幕刷新周期。在CADisplayLink加入到主運(yùn)行循環(huán)隊(duì)列后就會(huì)循環(huán)調(diào)用目標(biāo)方法,在這個(gè)方法中更新視圖內(nèi)容就可以完成逐幀動(dòng)畫(huà)。
當(dāng)然這里不得不強(qiáng)調(diào)的是逐幀動(dòng)畫(huà)性能勢(shì)必較低,但是對(duì)于一些事物的運(yùn)動(dòng)又不得不選擇使用逐幀動(dòng)畫(huà),例如人的運(yùn)動(dòng),這是一個(gè)高度復(fù)雜的運(yùn)動(dòng),基本動(dòng)畫(huà)、關(guān)鍵幀動(dòng)畫(huà)是不可能解決的。所大家一定要注意在循環(huán)方法中盡可能的降低算法復(fù)雜度,同時(shí)保證循環(huán)過(guò)程中內(nèi)存峰值盡可能低。下面以一個(gè)魚(yú)的運(yùn)動(dòng)為例為大家演示一下逐幀動(dòng)畫(huà)。
@interface CADisplaylinkAnimationViewController ()
@property (nonatomic, strong) CALayer *fishLayer;
@property (nonatomic, assign) NSInteger index;
@property (nonatomic, strong) NSMutableArray *images;
@end
@implementation CADisplaylinkAnimationViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.title = @"逐幀動(dòng)畫(huà)";
//創(chuàng)建圖像顯示圖層
self.fishLayer = [[CALayer alloc]init];
self.fishLayer.bounds = CGRectMake(0, 0, 250, 250);
self.fishLayer.position = CGPointMake(160, 150);
self.fishLayer.contents = (id)[UIImage imageNamed:@"timg_0001"].CGImage;
self.fishLayer.backgroundColor = [UIColor redColor].CGColor;
[self.view.layer addSublayer:self.fishLayer];
//由于魚(yú)的圖片在循環(huán)中會(huì)不斷創(chuàng)建,而幾張圖片相對(duì)較小
//與其在循環(huán)中不斷創(chuàng)建UIImage不如直接將所有圖片緩存起來(lái)
self.images = [NSMutableArray array];
for (int i = 0; i < 8; i++) {
NSString* imageName = [NSString stringWithFormat:@"timg_000%i.png",i];
UIImage* image = [UIImage imageNamed:imageName];
[self.images addObject:image];
}
//定義時(shí)鐘對(duì)象
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(stepFish)];
//將時(shí)鐘對(duì)象加入到主運(yùn)行循環(huán)RunLoop中
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
UIImageView* animImageView = [[UIImageView alloc]initWithFrame:CGRectMake(100, 400, 250, 250 )];
animImageView.animationImages = self.images;
animImageView.animationDuration = 0.8;
[animImageView startAnimating];
[self.view addSubview:animImageView];
}
#pragma mark 每次屏幕刷新就會(huì)執(zhí)行一次此方法(每秒接近60次)
- (void)stepFish {
//定義一個(gè)變量記錄執(zhí)行的次數(shù)
static int s = 0;
//每秒行6次
if (++s % 6 == 0) {
UIImage* image = self.images[self.index];
self.fishLayer.contents = (id)image.CGImage;
self.index = (self.index + 1) % 8;
}
}
@end
3 UIView動(dòng)畫(huà)封裝
有了前面核心動(dòng)畫(huà)的知識(shí),相信大家開(kāi)發(fā)出一般的動(dòng)畫(huà)效果應(yīng)該不在話下。在核心動(dòng)畫(huà)開(kāi)篇也給大家說(shuō)過(guò),其實(shí)UIView本身對(duì)于基本動(dòng)畫(huà)和關(guān)鍵幀動(dòng)畫(huà)、轉(zhuǎn)場(chǎng)動(dòng)畫(huà)都有相應(yīng)的封裝,在對(duì)動(dòng)畫(huà)細(xì)節(jié)沒(méi)有特殊要求的情況下使用起來(lái)也要簡(jiǎn)單的多??梢哉f(shuō)在日常開(kāi)發(fā)中90%以上的情況使用UIView的動(dòng)畫(huà)封裝方法都可以搞定,因此在熟悉了核心動(dòng)畫(huà)的原理之后還是有必要給大家簡(jiǎn)單介紹一下UIView中各類動(dòng)畫(huà)使用方法的。由于前面核心動(dòng)畫(huà)內(nèi)容已經(jīng)進(jìn)行過(guò)詳細(xì)介紹,學(xué)習(xí)UIView的封裝方法根本是小菜一碟,這里對(duì)于一些細(xì)節(jié)就不再贅述了。
3.1 基礎(chǔ)動(dòng)畫(huà)
基礎(chǔ)動(dòng)畫(huà)部分和前面的基礎(chǔ)動(dòng)畫(huà)演示相對(duì)應(yīng),演示點(diǎn)擊屏幕雪花飄落到鼠標(biāo)點(diǎn)擊位置的過(guò)程。注意根據(jù)前面介紹的隱式動(dòng)畫(huà)知識(shí)其實(shí)非根圖層直接設(shè)置終點(diǎn)位置不需要使用UIView的動(dòng)畫(huà)方法也可以實(shí)現(xiàn)動(dòng)畫(huà)效果,因此這里雪花不再放到圖層中而是放到了一個(gè)UIImageView中。下面的代碼演示了通過(guò)block和靜態(tài)方法實(shí)現(xiàn)動(dòng)畫(huà)控制的過(guò)程:
@implementation AnimationFromUIViewController {
UIImageView* _imageView;
UIImageView* _ball;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
//創(chuàng)建圖像顯示空間
_imageView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"snow"]];
_imageView.center = CGPointMake(50, 150);
[self.view addSubview:_imageView];
_ball = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"snowflake"]];
_ball.center = self.view.center;
[self.view addSubview:_ball];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
UITouch * touch = [touches anyObject];
CGPoint location = [touch locationInView:self.view];
[self startBasicAnimate:location];
[self startSpringAnimate:location];
}
- (void)startSpringAnimate:(CGPoint)location {
//創(chuàng)建阻尼動(dòng)畫(huà)
//damping:阻尼,范圍0-1,阻尼越接近于0,彈性效果越明顯
//velocity:彈性復(fù)位的速度
[UIView animateWithDuration:1.2 delay:0 usingSpringWithDamping:0.5 initialSpringVelocity:1.0 options:UIViewAnimationOptionCurveLinear animations:^{
_ball.center = location;
} completion:nil];
}
- (void)startBasicAnimate:(CGPoint)location {
//方法1;block方法
/*
開(kāi)始動(dòng)畫(huà),UIView的動(dòng)畫(huà)方法執(zhí)行完后動(dòng)畫(huà)會(huì)停留在重點(diǎn)位置,而不需要進(jìn)行任何特殊處理
duration:執(zhí)行時(shí)間
delay:延遲時(shí)間
option:動(dòng)畫(huà)設(shè)置,列如自動(dòng)恢復(fù),勻速運(yùn)動(dòng)等
completion:動(dòng)畫(huà)完成回調(diào)方法
*/
// [UIView animateWithDuration:1.5 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
// _imageView.center = location;
// } completion:^(BOOL finished) {
// NSLog(@"animate is end");
// }];
//方法2:靜態(tài)方法
//開(kāi)始動(dòng)畫(huà)
[UIView beginAnimations:@"ZXBasicAnimation" context:nil];
[UIView setAnimationDuration:1.5];
//[UIView setAnimationDelay:1.0];//設(shè)置延遲
//[UIView setAnimationRepeatAutoreverses:NO];//是否回復(fù)
//[UIView setAnimationRepeatCount:10];//重復(fù)次數(shù)
//[UIView setAnimationStartDate:(NSDate *)];//設(shè)置動(dòng)畫(huà)開(kāi)始運(yùn)行的時(shí)間
//[UIView setAnimationDelegate:self];//設(shè)置代理
//[UIView setAnimationWillStartSelector:(SEL)];//設(shè)置動(dòng)畫(huà)開(kāi)始運(yùn)動(dòng)的執(zhí)行方法
//[UIView setAnimationDidStopSelector:(SEL)];//設(shè)置動(dòng)畫(huà)運(yùn)行結(jié)束后的執(zhí)行方法
_imageView.center = location;
//開(kāi)始動(dòng)畫(huà)
[UIView commitAnimations];
}
@end

3.2 彈簧動(dòng)畫(huà)效果
由于在iOS開(kāi)發(fā)中彈性動(dòng)畫(huà)使用很普遍,所以在iOS7蘋(píng)果官方直接提供了一個(gè)方法用于彈性動(dòng)畫(huà)開(kāi)發(fā),下面簡(jiǎn)單的演示一下:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
//創(chuàng)建圖像顯示控件
_imageView=[[UIImageView alloc]initWithImage:[UIImage imageNamed:@"ball"]];
_imageView.size = CGSizeMake(100, 100);
_imageView.center=CGPointMake(160, 50);
[self.view addSubview:_imageView];
}
#pragma mark 點(diǎn)擊事件
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch=touches.anyObject;
CGPoint location= [touch locationInView:self.view];
/*創(chuàng)建彈性動(dòng)畫(huà)
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];
}

3.3 關(guān)鍵幀動(dòng)畫(huà)
從iOS7開(kāi)始UIView動(dòng)畫(huà)中封裝了關(guān)鍵幀動(dòng)畫(huà),下面就來(lái)看一下如何使用UIView封裝方法進(jìn)行關(guān)鍵幀動(dòng)畫(huà)控制,這里實(shí)現(xiàn)前面關(guān)鍵幀動(dòng)畫(huà)部分對(duì)于球的控制。
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//關(guān)鍵幀動(dòng)畫(huà),options
[UIView animateKeyframesWithDuration:5.0 delay:0 options:UIViewKeyframeAnimationOptionCalculationModeLinear animations:^{
//第二關(guān)鍵幀(準(zhǔn)確的說(shuō)第一個(gè)關(guān)鍵幀是開(kāi)始位置):從0秒開(kāi)始持續(xù)50%的時(shí)間,也就是5*0.5 = 2.5秒
[UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:0.5 animations:^{
_imageView.center = CGPointMake(80, 220);
}];
//第三幀,從0.5*5.0秒開(kāi)始,持續(xù)時(shí)間:5.0*0.25 = 1.25秒
[UIView addKeyframeWithRelativeStartTime:0.5 relativeDuration:0.25 animations:^{
_imageView.center = CGPointMake(40, 300);
}];
//第四幀,從0.75*5.0秒開(kāi)始,持續(xù)時(shí)間:5.0*0.25秒
[UIView addKeyframeWithRelativeStartTime:0.75f relativeDuration:0.25 animations:^{
_imageView.center = CGPointMake(67, 400);
}];
} completion:^(BOOL finished) {
NSLog(@"animation ended");
}];
/*
options 的補(bǔ)充
UIViewKeyframeAnimationOptionCalculationModeLinear:連續(xù)運(yùn)算模式。
UIViewKeyframeAnimationOptionCalculationModeDiscrete :離散運(yùn)算模式。
UIViewKeyframeAnimationOptionCalculationModePaced:均勻執(zhí)行運(yùn)算模式。
UIViewKeyframeAnimationOptionCalculationModeCubic:平滑運(yùn)算模式。
UIViewKeyframeAnimationOptionCalculationModeCubicPaced:平滑均勻運(yùn)算模式。
*/
}

注意:前面說(shuō)過(guò)關(guān)鍵幀動(dòng)畫(huà)有兩種形式,上面演示的是屬性值關(guān)鍵幀動(dòng)畫(huà),路徑關(guān)鍵幀動(dòng)畫(huà)目前UIView還不支持
3.4 轉(zhuǎn)場(chǎng)動(dòng)畫(huà)
從iOS4.0開(kāi)始,UIView直接封裝了轉(zhuǎn)場(chǎng)動(dòng)畫(huà),使用起來(lái)同樣很簡(jiǎn)單。
@implementation TransitionUIAnimationViewController {
UIImageView *_imageView;
int _currentIndex;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
//定義圖片控件
_imageView=[[UIImageView alloc]init];
_imageView.frame=[UIScreen mainScreen].applicationFrame;
_imageView.contentMode=UIViewContentModeScaleAspectFit;
_imageView.image=[UIImage imageNamed:@"0.jpg"];//默認(rèn)圖片
[self.view addSubview:_imageView];
//添加手勢(shì)
UISwipeGestureRecognizer *leftSwipeGesture = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(leftSwipe:)];
leftSwipeGesture.direction=UISwipeGestureRecognizerDirectionLeft;
[self.view addGestureRecognizer:leftSwipeGesture];
UISwipeGestureRecognizer *rightSwipeGesture = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(rightSwipe:)];
rightSwipeGesture.direction=UISwipeGestureRecognizerDirectionRight;
[self.view addGestureRecognizer:rightSwipeGesture];
}
#pragma mark 向左滑動(dòng)瀏覽下一張圖片
-(void)leftSwipe:(UISwipeGestureRecognizer *)gesture{
[self transitionAnimation:YES];
}
#pragma mark 向右滑動(dòng)瀏覽上一張圖片
-(void)rightSwipe:(UISwipeGestureRecognizer *)gesture{
[self transitionAnimation:NO];
}
#pragma mark 轉(zhuǎn)場(chǎng)動(dòng)畫(huà)
-(void)transitionAnimation:(BOOL)isNext{
UIViewAnimationOptions option;
if (isNext) {
option=UIViewAnimationOptionCurveLinear|UIViewAnimationOptionTransitionFlipFromRight;
} else {
option=UIViewAnimationOptionCurveLinear|UIViewAnimationOptionTransitionFlipFromLeft;
}
[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 % IMAGE_COUNT;
} else {
_currentIndex = --_currentIndex % IMAGE_COUNT;
}
NSString *imageName=[NSString stringWithFormat:@"%i.jpg",_currentIndex];
return [UIImage imageNamed:imageName];
}
@end

上面的轉(zhuǎn)場(chǎng)動(dòng)畫(huà)演示中,其實(shí)僅僅有一個(gè)視圖UIImageView做轉(zhuǎn)場(chǎng)動(dòng)畫(huà),每次轉(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)畫(huà)設(shè)置參數(shù)完全同基本動(dòng)畫(huà)參數(shù)設(shè)置;同直接使用轉(zhuǎn)場(chǎng)動(dòng)畫(huà)不同的是使用UIView的裝飾方法進(jìn)行轉(zhuǎn)場(chǎng)動(dòng)畫(huà)其動(dòng)畫(huà)效果較少,因?yàn)檫@里無(wú)法直接使用私有API。
本文參考zmmzxxx的CALayer與iOS動(dòng)畫(huà) 講解及使用,非常感謝。
- 如有錯(cuò)誤,歡迎指正,多多點(diǎn)贊,打賞更佳,您的支持是我寫(xiě)作的動(dòng)力。