iOS-Core Animation動(dòng)畫(huà)詳解

目錄
  • 一 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];
}
CoreAnimation.gif

使用上面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è)位置落花將飛向哪里

baseAnimation.gif
- (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;
}
baseAnimation2.gif

注意: 動(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"];
}
snow.gif

上面的方式固然比前面使用基礎(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"];
}
snow_path.gif

但是這里需要注意,對(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];
}
animation_group.gif
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]];
}
transition.gif

代碼十分簡(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
base.gif
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];
}
SpringAnimation.gif
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)算模式。
     */
}
KeyUiewAnimation.gif

注意:前面說(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
transUIAnimation.gif

上面的轉(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。


本文參考zmmzxxxCALayer與iOS動(dòng)畫(huà) 講解及使用,非常感謝。


  • 如有錯(cuò)誤,歡迎指正,多多點(diǎn)贊,打賞更佳,您的支持是我寫(xiě)作的動(dòng)力。

項(xiàng)目連接地址 - AnimationDemo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容