iOS開(kāi)發(fā)系列:讓你的應(yīng)用“動(dòng)”起來(lái)--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à)效果演示:

#import "KCMainViewController.h"

@interface KCMainViewController ()

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIImage *image=[UIImage imageNamed:@"open2.png"];
    UIImageView *imageView=[[UIImageView alloc]init];
    imageView.image=image;
    imageView.frame=CGRectMake(120, 140, 80, 80);
    [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];
}
@end

使用上面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框架中)。在iOS中核心動(dòng)畫(huà)分為幾類(lèi):基礎(chǔ)動(dòng)畫(huà)、關(guān)鍵幀動(dòng)畫(huà)、動(dòng)畫(huà)組、轉(zhuǎn)場(chǎng)動(dòng)畫(huà)。各個(gè)類(lèi)的關(guān)系大致如下:


  • CAAnimation:核心動(dòng)畫(huà)的基礎(chǔ)類(lèi),不能直接使用,負(fù)責(zé)動(dòng)畫(huà)運(yùn)行時(shí)間、速度的控制,本身實(shí)現(xiàn)了CAMediaTiming協(xié)議。

  • CAPropertyAnimation:屬性動(dòng)畫(huà)的基類(lèi)(通過(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à)。

基礎(chǔ)動(dòng)畫(huà)

在開(kāi)發(fā)過(guò)程中很多情況下通過(guò)基礎(chǔ)動(dòng)畫(huà)就可以滿足開(kāi)發(fā)需求,前面例子中使用的UIView代碼塊進(jìn)行圖像放大縮小的演示動(dòng)畫(huà)也是基礎(chǔ)動(dòng)畫(huà)(在iOS7中UIView也對(duì)關(guān)鍵幀動(dòng)畫(huà)進(jìn)行了封裝),只是UIView裝飾方法隱藏了更多的細(xì)節(jié)。如果不使用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è)位置落花將飛向哪里。

#import "KCMainViewController.h"

@interface KCMainViewController (){
    CALayer *_layer;
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //設(shè)置背景(注意這個(gè)圖片其實(shí)在根圖層)
    UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];
    self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];
    
    //自定義一個(gè)圖層
    _layer=[CALayer layer];
    _layer.bounds=CGRectMake(0, 0, 10, 20);
    _layer.position=CGPointMake(50, 150);
    _layer.contents=(id)[UIImage imageNamed:@"petal.png"].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=5.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í)可以使用此名稱(chēng)獲取
    [_layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Translation"];
}

#pragma mark 點(diǎn)擊事件
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    UITouch *touch=touches.anyObject;
    CGPoint location= [touch locationInView:self.view];
    //創(chuàng)建并開(kāi)始動(dòng)畫(huà)
    [self translatonAnimation:location];
}

@end

運(yùn)行效果:


上面實(shí)現(xiàn)了一個(gè)基本動(dòng)畫(huà)效果,但是這個(gè)動(dòng)畫(huà)存在一個(gè)問(wèn)題:動(dòng)畫(huà)結(jié)束后動(dòng)畫(huà)圖層回到了原來(lái)的位置,當(dāng)然是用UIView封裝的方法是沒(méi)有這個(gè)問(wèn)題的。如何解決這個(gè)問(wèn)題呢?

圖層動(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è)置它的位置。

//
// 

#import "KCMainViewController.h"

@interface KCMainViewController ()<CAAnimationDelegate>{
    CALayer *_layer;
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //設(shè)置背景(注意這個(gè)圖片其實(shí)在根圖層)
    UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];
    self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];
    
    //自定義一個(gè)圖層
    _layer=[[CALayer alloc]init];
    _layer.bounds=CGRectMake(0, 0, 10, 20);
    _layer.position=CGPointMake(50, 150);
    _layer.contents=(id)[UIImage imageNamed:@"petal.png"].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=5.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à)
    basicAnimation.delegate=self;
    //存儲(chǔ)當(dāng)前位置在動(dòng)畫(huà)結(jié)束后使用
    [basicAnimation setValue:[NSValue valueWithCGPoint:location] forKey:@"KCBasicAnimationLocation"];
    
    //3.添加動(dòng)畫(huà)到圖層,注意key相當(dāng)于給動(dòng)畫(huà)進(jìn)行命名,以后獲得該動(dòng)畫(huà)時(shí)可以使用此名稱(chēng)獲取
    [_layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Translation"];
}

#pragma mark 點(diǎn)擊事件
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    UITouch *touch=touches.anyObject;
    CGPoint location= [touch locationInView:self.view];
    //創(chuàng)建并開(kāi)始動(dòng)畫(huà)
    [self translatonAnimation:location];
}

#pragma mark - 動(dòng)畫(huà)代理方法
#pragma mark 動(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à)
}

#pragma mark 動(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];
}
@end

上面通過(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à)的背景。

要關(guān)閉隱式動(dòng)畫(huà)需要用到動(dòng)畫(huà)事務(wù)CATransaction,在事務(wù)內(nèi)將隱式動(dòng)畫(huà)關(guān)閉,例如上面的代碼可以改為:

#pragma mark 動(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)然上面的動(dòng)畫(huà)還顯得有些生硬,因?yàn)槁浠h散的時(shí)候可能不僅僅是自由落體運(yùn)動(dòng),本身由于空氣阻力、外界風(fēng)力還會(huì)造成落花在空中的旋轉(zhuǎn)、搖擺等,這里不妨給圖層添加一個(gè)旋轉(zhuǎn)的動(dòng)畫(huà)。對(duì)于圖層的旋轉(zhuǎn)通過(guò)key path設(shè)置圖層旋轉(zhuǎn)的內(nèi)容,在這里需要強(qiáng)調(diào)一下,圖層的形變都是基于錨點(diǎn)進(jìn)行的。例如旋轉(zhuǎn),旋轉(zhuǎn)的中心點(diǎn)就是圖層的錨點(diǎn)。

//

#import "KCMainViewController.h"

@interface KCMainViewController (){
    CALayer *_layer;
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //設(shè)置背景(注意這個(gè)圖片其實(shí)在根圖層)
    UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];
    self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];
    
    //自定義一個(gè)圖層
    _layer=[[CALayer alloc]init];
    _layer.bounds=CGRectMake(0, 0, 10, 20);
    _layer.position=CGPointMake(50, 150);
    _layer.anchorPoint=CGPointMake(0.5, 0.6);//設(shè)置錨點(diǎn)
    _layer.contents=(id)[UIImage imageNamed:@"petal.png"].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=5.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à)
    basicAnimation.delegate=self;
    //存儲(chǔ)當(dāng)前位置在動(dòng)畫(huà)結(jié)束后使用
    [basicAnimation setValue:[NSValue valueWithCGPoint:location] forKey:@"KCBasicAnimationLocation"];
    
    //3.添加動(dòng)畫(huà)到圖層,注意key相當(dāng)于給動(dòng)畫(huà)進(jìn)行命名,以后獲得該圖層時(shí)可以使用此名稱(chēng)獲取
    [_layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Translation"];
}

#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=6.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í)可以使用此名稱(chēng)獲取
    [_layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Rotation"];
}

#pragma mark 點(diǎn)擊事件
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    UITouch *touch=touches.anyObject;
    CGPoint location= [touch locationInView:self.view];
    //創(chuàng)建并開(kāi)始動(dòng)畫(huà)
    [self translatonAnimation:location];
    
    [self rotationAnimation];
}

#pragma mark - 動(dòng)畫(huà)代理方法
#pragma mark 動(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à)
}

#pragma mark 動(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];
}

@end

上面代碼中結(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)畫(huà)的運(yùn)行有一個(gè)媒體時(shí)間的概念,假設(shè)將一個(gè)旋轉(zhuǎn)動(dòng)畫(huà)設(shè)置旋轉(zhuǎn)一周用時(shí)60秒的話,那么當(dāng)動(dòng)畫(huà)旋轉(zhuǎn)90度后媒體時(shí)間就是15秒。如果此時(shí)要將動(dòng)畫(huà)暫停只需要讓媒體時(shí)間偏移量設(shè)置為15秒即可,并把動(dòng)畫(huà)運(yùn)行速度設(shè)置為0使其停止運(yùn)動(dòng)。類(lèi)似的,如果又過(guò)了60秒后需要恢復(fù)動(dòng)畫(huà)(此時(shí)媒體時(shí)間為75秒),這時(shí)只要將動(dòng)畫(huà)開(kāi)始開(kāi)始時(shí)間設(shè)置為當(dāng)前媒體時(shí)間75秒減去暫停時(shí)的時(shí)間(也就是之前定格動(dòng)畫(huà)時(shí)的偏移量)15秒(開(kāi)始時(shí)間=75-15=60秒),那么動(dòng)畫(huà)就會(huì)重新計(jì)算60秒后的狀態(tài)再開(kāi)始運(yùn)行,與此同時(shí)將偏移量重新設(shè)置為0并且把運(yùn)行速度設(shè)置1。這個(gè)過(guò)程中真正起到暫停動(dòng)畫(huà)和恢復(fù)動(dòng)畫(huà)的其實(shí)是動(dòng)畫(huà)速度的調(diào)整,媒體時(shí)間偏移量以及恢復(fù)時(shí)的開(kāi)始時(shí)間設(shè)置主要為了讓動(dòng)畫(huà)更加連貫。

下面的代碼演示了移動(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ù))。

//

#import "KCMainViewController.h"

@interface KCMainViewController (){
    CALayer *_layer;
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //設(shè)置背景(注意這個(gè)圖片其實(shí)在根圖層)
    UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];
    self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];
    
    //自定義一個(gè)圖層
    _layer=[[CALayer alloc]init];
    _layer.bounds=CGRectMake(0, 0, 10, 20);
    _layer.position=CGPointMake(50, 150);
    _layer.anchorPoint=CGPointMake(0.5, 0.6);//設(shè)置錨點(diǎn)
    _layer.contents=(id)[UIImage imageNamed:@"petal.png"].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=5.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à)
    basicAnimation.delegate=self;
    //存儲(chǔ)當(dāng)前位置在動(dòng)畫(huà)結(jié)束后使用
    [basicAnimation setValue:[NSValue valueWithCGPoint:location] forKey:@"KCBasicAnimationLocation"];
    
    //3.添加動(dòng)畫(huà)到圖層,注意key相當(dāng)于給動(dòng)畫(huà)進(jìn)行命名,以后獲得該圖層時(shí)可以使用此名稱(chēng)獲取
    [_layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Translation"];
}

#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=6.0;
    basicAnimation.autoreverses=true;//旋轉(zhuǎn)后在旋轉(zhuǎn)到原來(lái)的位置
    basicAnimation.repeatCount=HUGE_VALF;//設(shè)置無(wú)限循環(huán)
    basicAnimation.removedOnCompletion=NO;
//    basicAnimation.delegate=self;

    
    //4.添加動(dòng)畫(huà)到圖層,注意key相當(dāng)于給動(dòng)畫(huà)進(jìn)行命名,以后獲得該動(dòng)畫(huà)時(shí)可以使用此名稱(chēng)獲取
    [_layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Rotation"];
}

#pragma mark 點(diǎn)擊事件
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    UITouch *touch=touches.anyObject;
    CGPoint location= [touch locationInView:self.view];
    //判斷是否已經(jīng)創(chuàng)建過(guò)動(dòng)畫(huà),如果已經(jīng)創(chuàng)建則不再創(chuàng)建動(dòng)畫(huà)
    CAAnimation *animation= [_layer animationForKey:@"KCBasicAnimation_Translation"];
    if(animation){
        if (_layer.speed==0) {
            [self animationResume];
        }else{
            [self animationPause];
        }
    }else{
        //創(chuàng)建并開(kāi)始動(dòng)畫(huà)
        [self translatonAnimation:location];
        
        [self rotationAnimation];
    }
}

#pragma mark 動(dòng)畫(huà)暫停
-(void)animationPause{
    //取得指定圖層動(dòng)畫(huà)的媒體時(shí)間,后面參數(shù)用于指定子圖層,這里不需要
    CFTimeInterval interval=[_layer convertTime:CACurrentMediaTime() fromLayer:nil];
    //設(shè)置時(shí)間偏移量,保證暫停時(shí)停留在旋轉(zhuǎn)的位置
    [_layer setTimeOffset:interval];
    //速度設(shè)置為0,暫停動(dòng)畫(huà)
    _layer.speed=0;
}

#pragma mark 動(dòng)畫(huà)恢復(fù)
-(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;
}

#pragma mark - 動(dòng)畫(huà)代理方法
#pragma mark 動(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à)
}

#pragma mark 動(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à)
    [self animationPause];

}

@end

運(yùn)行效果:


注意:
動(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ì)銷(xiāo)毀。

關(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à)稱(chēng)為“補(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è)置了路徑則屬性值就不再起作用。

對(duì)于前面的落花動(dòng)畫(huà)效果而言其實(shí)落花的過(guò)程并不自然,很顯然實(shí)際生活中它不可能沿著直線下落,這里我們不妨通過(guò)關(guān)鍵幀動(dòng)畫(huà)的values屬性控制它在下落過(guò)程中的屬性。假設(shè)下落過(guò)程如圖:



在這里需要設(shè)置四個(gè)關(guān)鍵幀(如圖中四個(gè)關(guān)鍵點(diǎn)),具體代碼如下(動(dòng)畫(huà)創(chuàng)建過(guò)程同基本動(dòng)畫(huà)基本完全一致):

//

#import "KCMainViewController.h"

@interface KCMainViewController (){
    CALayer *_layer;
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //設(shè)置背景(注意這個(gè)圖片其實(shí)在根圖層)
    UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];
    self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];

    //自定義一個(gè)圖層
    _layer=[[CALayer alloc]init];
    _layer.bounds=CGRectMake(0, 0, 10, 20);
    _layer.position=CGPointMake(50, 150);
    _layer.contents=(id)[UIImage imageNamed:@"petal.png"].CGImage;
    [self.view.layer addSublayer:_layer];
    
    //創(chuàng)建動(dòng)畫(huà)
    [self translationAnimation];
}

#pragma mark 關(guān)鍵幀動(dòng)畫(huà)
-(void)translationAnimation{
    //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:_layer.position];//對(duì)于關(guān)鍵幀動(dòng)畫(huà)初始值不能省略
    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)畫(huà)到圖層,添加動(dòng)畫(huà)后就會(huì)執(zhí)行動(dòng)畫(huà)
    [_layer addAnimation:keyframeAnimation forKey:@"KCKeyframeAnimation_Position"];
}

@end

運(yùn)行效果(注意運(yùn)行結(jié)束沒(méi)有設(shè)置圖層位置為動(dòng)畫(huà)運(yùn)動(dòng)結(jié)束位置):



上面的方式固然比前面使用基礎(chǔ)動(dòng)畫(huà)效果要好一些,但其實(shí)還是存在問(wèn)題,那就是落花飛落的路徑是直線的,當(dāng)然這個(gè)直線是根據(jù)程序中設(shè)置的四個(gè)關(guān)鍵幀自動(dòng)形成的,那么如何讓它沿著曲線飄落呢?這就是第二種類(lèi)型的關(guān)鍵幀動(dòng)畫(huà),通過(guò)描繪路徑進(jìn)行關(guān)鍵幀動(dòng)畫(huà)控制。假設(shè)讓落花沿著下面的曲線路徑飄落:



當(dāng)然,這是一條貝塞爾曲線,學(xué)習(xí)了前篇文章之后相信對(duì)于這類(lèi)曲線應(yīng)該并不陌生,下面是具體實(shí)現(xiàn)代碼:
//

#import "KCMainViewController.h"

@interface KCMainViewController (){
    CALayer *_layer;
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //設(shè)置背景(注意這個(gè)圖片其實(shí)在根圖層)
    UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];
    self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];
    
    //自定義一個(gè)圖層
    _layer=[[CALayer alloc]init];
    _layer.bounds=CGRectMake(0, 0, 10, 20);
    _layer.position=CGPointMake(50, 150);
    _layer.contents=(id)[UIImage imageNamed:@"petal.png"].CGImage;
    [self.view.layer addSublayer:_layer];
    
    //創(chuàng)建動(dòng)畫(huà)
    [self translationAnimation];
}

#pragma mark 關(guān)鍵幀動(dòng)畫(huà)
-(void)translationAnimation{
    //1.創(chuàng)建關(guān)鍵幀動(dòng)畫(huà)并設(shè)置動(dòng)畫(huà)屬性
    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()+5;//設(shè)置延遲2秒執(zhí)行
    
    //3.添加動(dòng)畫(huà)到圖層,添加動(dòng)畫(huà)后就會(huì)執(zhí)行動(dòng)畫(huà)
    [_layer addAnimation:keyframeAnimation forKey:@"KCKeyframeAnimation_Position"];
}

@end

運(yùn)行效果(注意運(yùn)行結(jié)束沒(méi)有設(shè)置圖層位置為動(dòng)畫(huà)運(yùn)動(dòng)結(jié)束位置):


看起來(lái)動(dòng)畫(huà)不會(huì)那么生硬了,但是這里需要注意,對(duì)于路徑類(lèi)型的關(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é)者往往比較容易混淆,這里專(zhuān)門(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í)行)。

下圖描繪出了幾種動(dòng)畫(huà)模式的關(guān)系(橫坐標(biāo)是運(yùn)行時(shí)間,縱坐標(biāo)是動(dòng)畫(huà)屬性[例如位置、透明度等]):


動(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)各類(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à)效果。

動(dòng)畫(huà)組使用起來(lái)并不復(fù)雜,首先單獨(dú)創(chuàng)建單個(gè)動(dòng)畫(huà)(可以是基礎(chǔ)動(dòng)畫(huà)也可以是關(guān)鍵幀動(dòng)畫(huà)),然后將基礎(chǔ)動(dòng)畫(huà)添加到動(dòng)畫(huà)組,最后將動(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)更加的和諧、順暢。

//

#import "KCMainViewController.h"

@interface KCMainViewController (){
    CALayer *_layer;
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //設(shè)置背景(注意這個(gè)圖片其實(shí)在根圖層)
    UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];
    self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];
    
    //自定義一個(gè)圖層
    _layer=[[CALayer alloc]init];
    _layer.bounds=CGRectMake(0, 0, 10, 20);
    _layer.position=CGPointMake(50, 150);
    _layer.contents=(id)[UIImage imageNamed:@"petal.png"].CGImage;
    [self.view.layer addSublayer:_layer];
    
    //創(chuàng)建動(dòng)畫(huà)
    [self groupAnimation];
}

#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:M_PI_2*3];

//    basicAnimation.duration=6.0;
    basicAnimation.autoreverses=true;
    basicAnimation.repeatCount=HUGE_VALF;
    basicAnimation.removedOnCompletion=NO;
    
    [basicAnimation setValue:[NSNumber numberWithFloat:toValue] forKey:@"KCBasicAnimationProperty_ToValue"];
    
    return basicAnimation;
}

#pragma mark 關(guān)鍵幀移動(dòng)動(dòng)畫(huà)
-(CAKeyframeAnimation *)translationAnimation{
    CAKeyframeAnimation *keyframeAnimation=[CAKeyframeAnimation animationWithKeyPath:@"position"];
    
    CGPoint endPoint= CGPointMake(55, 400);
    CGPathRef path=CGPathCreateMutable();
    CGPathMoveToPoint(path, NULL, _layer.position.x, _layer.position.y);
    CGPathAddCurveToPoint(path, NULL, 160, 280, -30, 300, endPoint.x, endPoint.y);
    
    keyframeAnimation.path=path;
    CGPathRelease(path);

    [keyframeAnimation setValue:[NSValue valueWithCGPoint:endPoint] forKey:@"KCKeyframeAnimationProperty_EndPosition"];
    
    return keyframeAnimation;
}

#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;//延遲五秒執(zhí)行
    
    //3.給圖層添加動(dòng)畫(huà)
    [_layer addAnimation:animationGroup forKey:nil];
}

#pragma mark - 代理方法
#pragma mark 動(dòng)畫(huà)完成
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
    CAAnimationGroup *animationGroup=(CAAnimationGroup *)anim;
    CABasicAnimation *basicAnimation=animationGroup.animations[0];
    CAKeyframeAnimation *keyframeAnimation=animationGroup.animations[1];
    CGFloat toValue=[[basicAnimation valueForKey:@"KCBasicAnimationProperty_ToValue"] floatValue];
    CGPoint endPoint=[[keyframeAnimation valueForKey:@"KCKeyframeAnimationProperty_EndPosition"] CGPointValue];
    
    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    
    //設(shè)置動(dòng)畫(huà)最終狀態(tài)
    _layer.position=endPoint;
    _layer.transform=CATransform3DMakeRotation(toValue, 0, 0, 1);
    
    [CATransaction commit];
}

@end

運(yùn)行效果:


轉(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è)步驟:

  1. 創(chuàng)建轉(zhuǎn)場(chǎng)動(dòng)畫(huà)

  2. 設(shè)置轉(zhuǎn)場(chǎng)類(lèi)型、子類(lèi)型(可選)及其他屬性

  3. 設(shè)置轉(zhuǎn)場(chǎng)后的新視圖并添加動(dòng)畫(huà)到圖層

下表列出了常用的轉(zhuǎn)場(chǎng)類(lèi)型(注意私有API是蘋(píng)果官方?jīng)]有公開(kāi)的動(dòng)畫(huà)類(lèi)型,但是目前通過(guò)仍然可以使用):

動(dòng)畫(huà)類(lèi)型 說(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)畫(huà)類(lèi)型還包含子類(lèi)型:

動(dòng)畫(huà)子類(lèi)型 說(shuō)明
kCATransitionFromRight 從右側(cè)轉(zhuǎn)場(chǎng)
kCATransitionFromLeft 從左側(cè)轉(zhuǎn)場(chǎng)
kCATransitionFromTop 從頂部轉(zhuǎn)場(chǎng)
kCATransitionFromBottom 從底部轉(zhuǎn)場(chǎng)

使用轉(zhuǎn)場(chǎng)動(dòng)畫(huà)利用一個(gè)UIImageView實(shí)現(xiàn)一個(gè)漂亮的無(wú)限循環(huán)圖片瀏覽器:

//

#import "KCMainViewController.h"
#define IMAGE_COUNT 5

@interface KCMainViewController (){
    UIImageView *_imageView;
    int _currentIndex;
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //定義圖片控件
    _imageView=[[UIImageView alloc]init];
    _imageView.frame=[UIScreen mainScreen].bounds;
    _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{
    //1.創(chuàng)建轉(zhuǎn)場(chǎng)動(dòng)畫(huà)對(duì)象
    CATransition *transition=[[CATransition alloc]init];
    
    //2.設(shè)置動(dòng)畫(huà)類(lèi)型,注意對(duì)于蘋(píng)果官方?jīng)]公開(kāi)的動(dòng)畫(huà)類(lèi)型只能使用字符串,并沒(méi)有對(duì)應(yīng)的常量定義
    transition.type=@"cube";
    
    //設(shè)置子類(lèi)型
    if (isNext) {
        transition.subtype=kCATransitionFromRight;
    }else{
        transition.subtype=kCATransitionFromLeft;
    }
    //設(shè)置動(dòng)畫(huà)時(shí)常
    transition.duration=1.0f;
    
    //3.設(shè)置轉(zhuǎn)場(chǎng)后的新視圖添加轉(zhuǎn)場(chǎng)動(dòng)畫(huà)
    _imageView.image=[self getImage:isNext];
    [_imageView.layer addAnimation:transition forKey:@"KCTransitionAnimation"];
}

#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];
}
@end

運(yùn)行效果:

代碼十分簡(jiǎn)單,但是效果和性能卻很驚人。當(dāng)然演示代碼有限,其他動(dòng)畫(huà)類(lèi)型大家可以自己實(shí)現(xiàn),效果都很絢麗。

逐幀動(dòng)畫(huà)

前面介紹了核心動(dòng)畫(huà)中大部分動(dòng)畫(huà)類(lèi)型,但是做過(guò)動(dòng)畫(huà)處理的朋友都知道,在動(dòng)畫(huà)制作中還有一種動(dòng)畫(huà)類(lèi)型“逐幀動(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à)類(lèi)型,但是卻提供了用于完成逐幀動(dòng)畫(huà)的相關(guān)對(duì)象CADisplayLink。CADisplayLink是一個(gè)計(jì)時(shí)器,但是同NSTimer不同的是,CADisplayLink的刷新周期同屏幕完全一致。例如在iOS中屏幕刷新周期是60次/秒,CADisplayLink刷新周期同屏幕刷新一致也是60次/秒,這樣一來(lái)使用它完成的逐幀動(dòng)畫(huà)(又稱(chēng)為“時(shí)鐘動(dòng)畫(huà)”)完全感覺(jué)不到動(dòng)畫(huà)的停滯情況。

iOS程序在運(yùn)行后就進(jìn)入一個(gè)消息循環(huán)中(這個(gè)消息循環(huán)稱(chēng)為“主運(yùn)行循環(huán)”),整個(gè)程序相當(dāng)于進(jìn)入一個(gè)死循環(huán)中,始終等待用戶(hù)輸入。將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à)。

//

#import "KCMainViewController.h"
#define IMAGE_COUNT 10

@interface KCMainViewController (){
    CALayer *_layer;
    int _index;
    NSMutableArray *_images;
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //設(shè)置背景
    self.view.layer.contents=(id)[UIImage imageNamed:@"bg.png"].CGImage;
    
    //創(chuàng)建圖像顯示圖層
    _layer=[[CALayer alloc]init];
    _layer.bounds=CGRectMake(0, 0, 87, 32);
    _layer.position=CGPointMake(160, 284);
    [self.view.layer addSublayer:_layer];
    
    //由于魚(yú)的圖片在循環(huán)中會(huì)不斷創(chuàng)建,而10張魚(yú)的照片相對(duì)都很小
    //與其在循環(huán)中不斷創(chuàng)建UIImage不如直接將10張圖片緩存起來(lái)
    _images=[NSMutableArray array];
    for (int i=0; i<10; ++i) {
        NSString *imageName=[NSString stringWithFormat:@"fish%i.png",i];
        UIImage *image=[UIImage imageNamed:imageName];
        [_images addObject:image];
    }

    
    //定義時(shí)鐘對(duì)象
    CADisplayLink *displayLink=[CADisplayLink displayLinkWithTarget:self selector:@selector(step)];
    //添加時(shí)鐘對(duì)象到主運(yùn)行循環(huán)
    [displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}

#pragma mark 每次屏幕刷新就會(huì)執(zhí)行一次此方法(每秒接近60次)
-(void)step{
    //定義一個(gè)變量記錄執(zhí)行次數(shù)
    static int s=0;
    //每秒執(zhí)行6次
    if (++s%10==0) {
        UIImage *image=_images[_index];
        _layer.contents=(id)image.CGImage;//更新圖片
        _index=(_index+1)%IMAGE_COUNT;
    }
}
@end

運(yùn)行效果:

注意:上面僅僅演示了逐幀動(dòng)畫(huà)的過(guò)程,事實(shí)上結(jié)合其他動(dòng)畫(huà)類(lèi)型可以讓整條魚(yú)游動(dòng)起來(lái),這里不再贅述。

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)單的多。可以說(shuō)在日常開(kāi)發(fā)中90%以上的情況使用UIView的動(dòng)畫(huà)封裝方法都可以搞定,因此在熟悉了核心動(dòng)畫(huà)的原理之后還是有必要給大家簡(jiǎn)單介紹一下UIView中各類(lèi)動(dòng)畫(huà)使用方法的。由于前面核心動(dòng)畫(huà)內(nèi)容已經(jīng)進(jìn)行過(guò)詳細(xì)介紹,學(xué)習(xí)UIView的封裝方法根本是小菜一碟,這里對(duì)于一些細(xì)節(jié)就不再贅述了。

基礎(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ò)程:

//

#import "KCMainViewController.h"

@interface KCMainViewController (){
    UIImageView *_imageView;
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    //設(shè)置背景
    UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];
    self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];

    //創(chuàng)建圖像顯示控件
    _imageView=[[UIImageView alloc]initWithImage:[UIImage imageNamed:@"petal.png"]];
    _imageView.center=CGPointMake(50, 150);
    [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];
    //方法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í)間
     options:動(dòng)畫(huà)設(shè)置,例如自動(dòng)恢復(fù)、勻速運(yùn)動(dòng)等
     completion:動(dòng)畫(huà)完成回調(diào)方法
     */
//    [UIView animateWithDuration:5.0 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
//        _imageView.center=location;
//    } completion:^(BOOL finished) {
//        NSLog(@"Animation end.");
//    }];
    
    //方法2:靜態(tài)方法,較方法1好些
    //開(kāi)始動(dòng)畫(huà)
    [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)畫(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

補(bǔ)充--彈簧動(dòng)畫(huà)效果

由于在iOS開(kāi)發(fā)中彈性動(dòng)畫(huà)使用很普遍,所以在iOS7蘋(píng)果官方直接提供了一個(gè)方法用于彈性動(dòng)畫(huà)開(kāi)發(fā),下面簡(jiǎn)單的演示一下:

//

#import "KCMainViewController.h"

@interface KCMainViewController (){
    UIImageView *_imageView;
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    //創(chuàng)建圖像顯示控件
    _imageView=[[UIImageView alloc]initWithImage:[UIImage imageNamed:@"ball.png"]];
    _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];
}
@end

運(yùn)行效果:

補(bǔ)充--動(dòng)畫(huà)設(shè)置參數(shù)

在動(dòng)畫(huà)方法中有一個(gè)option參數(shù),UIViewAnimationOptions類(lèi)型,它是一個(gè)枚舉類(lèi)型,動(dòng)畫(huà)參數(shù)分為三類(lèi),可以組合使用:

  1. 常規(guī)動(dòng)畫(huà)屬性設(shè)置(可以同時(shí)選擇多個(gè)進(jìn)行設(shè)置)

UIViewAnimationOptionLayoutSubviews:動(dòng)畫(huà)過(guò)程中保證子視圖跟隨運(yùn)動(dòng)。
UIViewAnimationOptionAllowUserInteraction:動(dòng)畫(huà)過(guò)程中允許用戶(hù)交互。
UIViewAnimationOptionBeginFromCurrentState:所有視圖從當(dāng)前狀態(tài)開(kāi)始運(yùn)行。
UIViewAnimationOptionRepeat:重復(fù)運(yùn)行動(dòng)畫(huà)。
UIViewAnimationOptionAutoreverse :動(dòng)畫(huà)運(yùn)行到結(jié)束點(diǎn)后仍然以動(dòng)畫(huà)方式回到初始點(diǎn)。
UIViewAnimationOptionOverrideInheritedDuration:忽略嵌套動(dòng)畫(huà)時(shí)間設(shè)置。
UIViewAnimationOptionOverrideInheritedCurve:忽略嵌套動(dòng)畫(huà)速度設(shè)置。
UIViewAnimationOptionAllowAnimatedContent:動(dòng)畫(huà)過(guò)程中重繪視圖(注意僅僅適用于轉(zhuǎn)場(chǎng)動(dòng)畫(huà))。
UIViewAnimationOptionShowHideTransitionViews:視圖切換時(shí)直接隱藏舊視圖、顯示新視圖,而不是將舊視圖從父視圖移除(僅僅適用于轉(zhuǎn)場(chǎng)動(dòng)畫(huà))
UIViewAnimationOptionOverrideInheritedOptions :不繼承父動(dòng)畫(huà)設(shè)置或動(dòng)畫(huà)類(lèi)型。

  1. 動(dòng)畫(huà)速度控制(可從其中選擇一個(gè)設(shè)置)

UIViewAnimationOptionCurveEaseInOut:動(dòng)畫(huà)開(kāi)始緩慢,中間快,結(jié)束慢。
UIViewAnimationOptionCurveEaseIn :動(dòng)畫(huà)開(kāi)始慢,之后逐漸加速。
UIViewAnimationOptionCurveEaseOut:動(dòng)畫(huà)開(kāi)始快,之后逐漸變慢。
UIViewAnimationOptionCurveLinear :動(dòng)畫(huà)勻速執(zhí)行,默認(rèn)值。

  1. 轉(zhuǎn)場(chǎng)類(lèi)型(僅適用于轉(zhuǎn)場(chǎng)動(dòng)畫(huà)設(shè)置,可以從中選擇一個(gè)進(jìn)行設(shè)置,基本動(dòng)畫(huà)、關(guān)鍵幀動(dòng)畫(huà)不需要設(shè)置)

UIViewAnimationOptionTransitionNone:沒(méi)有轉(zhuǎn)場(chǎng)動(dòng)畫(huà)效果。
UIViewAnimationOptionTransitionFlipFromLeft :從左側(cè)翻轉(zhuǎn)效果。
UIViewAnimationOptionTransitionFlipFromRight:從右側(cè)翻轉(zhuǎn)效果。
UIViewAnimationOptionTransitionCurlUp:向后翻頁(yè)的動(dòng)畫(huà)過(guò)渡效果。
UIViewAnimationOptionTransitionCurlDown :向前翻頁(yè)的動(dòng)畫(huà)過(guò)渡效果。
UIViewAnimationOptionTransitionCrossDissolve:舊視圖溶解消失顯示下一個(gè)新視圖的效果。
UIViewAnimationOptionTransitionFlipFromTop :從上方翻轉(zhuǎn)效果。
UIViewAnimationOptionTransitionFlipFromBottom:從底部翻轉(zhuǎn)效果。

關(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ì)于落花的控制。

//

#import "KCMainViewController.h"

@interface KCMainViewController (){
    UIImageView *_imageView;
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //設(shè)置背景
    UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];
    self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];

    //創(chuàng)建圖像顯示控件
    _imageView=[[UIImageView alloc]initWithImage:[UIImage imageNamed:@"petal.png"]];
    _imageView.center=CGPointMake(50, 150);
    [self.view addSubview:_imageView];
}

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    //UITouch *touch=touches.anyObject;
    //CGPoint location= [touch locationInView:self.view];
    
    /*關(guān)鍵幀動(dòng)畫(huà)
     options:
     */
    [UIView animateKeyframesWithDuration:5.0 delay:0 options: UIViewKeyframeAnimationOptionCalculationModeLinear 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.");
    }];
}
@end

補(bǔ)充--動(dòng)畫(huà)設(shè)置參數(shù)

對(duì)于關(guān)鍵幀動(dòng)畫(huà)也有一些動(dòng)畫(huà)參數(shù)設(shè)置options,UIViewKeyframeAnimationOptions類(lèi)型,和上面基本動(dòng)畫(huà)參數(shù)設(shè)置有些差別,關(guān)鍵幀動(dòng)畫(huà)設(shè)置參數(shù)分為兩類(lèi),可以組合使用:

  1. 常規(guī)動(dòng)畫(huà)屬性設(shè)置(可以同時(shí)選擇多個(gè)進(jìn)行設(shè)置)

UIViewAnimationOptionLayoutSubviews:動(dòng)畫(huà)過(guò)程中保證子視圖跟隨運(yùn)動(dòng)。
UIViewAnimationOptionAllowUserInteraction:動(dòng)畫(huà)過(guò)程中允許用戶(hù)交互。
UIViewAnimationOptionBeginFromCurrentState:所有視圖從當(dāng)前狀態(tài)開(kāi)始運(yùn)行。
UIViewAnimationOptionRepeat:重復(fù)運(yùn)行動(dòng)畫(huà)。
UIViewAnimationOptionAutoreverse :動(dòng)畫(huà)運(yùn)行到結(jié)束點(diǎn)后仍然以動(dòng)畫(huà)方式回到初始點(diǎn)。
UIViewAnimationOptionOverrideInheritedDuration:忽略嵌套動(dòng)畫(huà)時(shí)間設(shè)置。
UIViewAnimationOptionOverrideInheritedOptions :不繼承父動(dòng)畫(huà)設(shè)置或動(dòng)畫(huà)類(lèi)型。

  1. 動(dòng)畫(huà)模式設(shè)置(同前面關(guān)鍵幀動(dòng)畫(huà)動(dòng)畫(huà)模式一一對(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)畫(huà)有兩種形式,上面演示的是屬性值關(guān)鍵幀動(dòng)畫(huà),路徑關(guān)鍵幀動(dòng)畫(huà)目前UIView還不支持。

轉(zhuǎn)場(chǎng)動(dòng)畫(huà)

從iOS4.0開(kāi)始,UIView直接封裝了轉(zhuǎn)場(chǎng)動(dòng)畫(huà),使用起來(lái)同樣很簡(jiǎn)單。

//

#import "KCMainViewController.h"
#define IMAGE_COUNT 5

@interface KCMainViewController (){
    UIImageView *_imageView;
    int _currentIndex;
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //定義圖片控件
    _imageView=[[UIImageView alloc]init];
    _imageView.frame=[UIScreen mainScreen].bounds;
    _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+1)%IMAGE_COUNT;
    }else{
        _currentIndex=(_currentIndex-1+IMAGE_COUNT)%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。

上一篇:iOS開(kāi)發(fā)系列:讓你的應(yīng)用“動(dòng)”起來(lái)--CALayer
下一篇:iOS開(kāi)發(fā)之多線程GCD

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,936評(píng)論 6 535
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,744評(píng)論 3 421
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,879評(píng)論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,181評(píng)論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,935評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,325評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,384評(píng)論 3 443
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,534評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,084評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,892評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,067評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,623評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,322評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,735評(píng)論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,990評(píng)論 1 289
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,800評(píng)論 3 395
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,084評(píng)論 2 375

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

  • 概覽 在iOS中隨處都可以看到絢麗的動(dòng)畫(huà)效果,實(shí)現(xiàn)這些動(dòng)畫(huà)的過(guò)程并不復(fù)雜,今天將帶大家一窺iOS動(dòng)畫(huà)全貌。在這里你...
    Yiart閱讀 3,851評(píng)論 3 34
  • 在iOS中隨處都可以看到絢麗的動(dòng)畫(huà)效果,實(shí)現(xiàn)這些動(dòng)畫(huà)的過(guò)程并不復(fù)雜,今天將帶大家一窺ios動(dòng)畫(huà)全貌。在這里你可以看...
    每天刷兩次牙閱讀 8,544評(píng)論 6 30
  • 在iOS中隨處都可以看到絢麗的動(dòng)畫(huà)效果,實(shí)現(xiàn)這些動(dòng)畫(huà)的過(guò)程并不復(fù)雜,今天將帶大家一窺iOS動(dòng)畫(huà)全貌。在這里你可以看...
    F麥子閱讀 5,132評(píng)論 5 13
  • 前言 本文只要描述了iOS中的Core Animation(核心動(dòng)畫(huà):隱式動(dòng)畫(huà)、顯示動(dòng)畫(huà))、貝塞爾曲線、UIVie...
    GitHubPorter閱讀 3,646評(píng)論 7 11
  • 概覽 在iOS中隨處都可以看到絢麗的動(dòng)畫(huà)效果,實(shí)現(xiàn)這些動(dòng)畫(huà)的過(guò)程并不復(fù)雜,今天將帶大家一窺iOS動(dòng)畫(huà)全貌。在這里你...
    被吹落的風(fēng)閱讀 1,582評(píng)論 1 2