iOS 動畫篇 - Core Animation

聲明

該篇文章的內(nèi)容參考自 iOS核心動畫高級技巧 一文距辆,非常感謝其作者和中文版的作者芜果,讓我能夠相對系統(tǒng)的學(xué)習(xí) CoreAnimation 的知識换薄,我受益匪淺,再次感謝翔试。

如果有興趣的小伙伴可以訪問其網(wǎng)站轻要,詳細(xì)的,完整的學(xué)習(xí) CoreAnimation垦缅。

CAAnimation 篇

CAAnimation 是一個抽象動畫類冲泥。 遵循著 CAMediaTiming 和 CAAciotn 兩個協(xié)議。 要為 Core Animation 圖層或 Scene Kit 對象設(shè)置動畫,請創(chuàng)建其子類 CABasicAnimation凡恍,CAKeyframeAnimation志秃,CAAnimationGroup 或 CATransition 的實例。Core Animation 可以用在 Mac OS X 和 iOS 平臺嚼酝。Core Animation 的動畫執(zhí)行過程都是在后臺操作的浮还,不會阻塞主線程。

隱式動畫

當(dāng)你改變 CALayer 的一個可做動畫的屬性闽巩,它并不會立刻在屏幕上呈現(xiàn)出來钧舌,而是從先前的值平滑過渡到新值。典型的例子就是改變圖層的背景填充色涎跨。

示例:

假如我們現(xiàn)在有一個圖層洼冻,那我們在點擊屏幕時嘗試去改變此圖層的背景填充色。

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    // 生成隨機顏色
    CGFloat red = arc4random() / (CGFloat)INT_MAX;
    CGFloat green = arc4random() / (CGFloat)INT_MAX;
    CGFloat blue = arc4random() / (CGFloat)INT_MAX;
    self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
}
隱式動畫

我們可以看到隅很,但我們點擊屏幕以改變圖層的背景時撞牢,視圖從舊的背景逐漸地過度到了新值。在這過程中叔营,我們沒有做其他額外的操作屋彪,這種自行完成的平滑過渡動畫就是隱式動畫。

事務(wù)

那么這一過程時如何完成的呢审编?實際上動畫是由當(dāng)前事務(wù)來完成的撼班,事務(wù)是什么?事務(wù)是 Core Animation 用來包含一系列屬性動畫集合的機制垒酬,你可以設(shè)置動畫的執(zhí)行時間等砰嘁,這些動畫的圖層屬性新值的設(shè)置都不會立刻發(fā)生變化,而是當(dāng)事務(wù)提交時由 run loop 自動開始勘究。

事務(wù)是通過 CATransaction 類來管理矮湘。該類沒有屬性或者實例方法,因此你不能創(chuàng)建它口糕,但是你可以通過 +begincommit 來將當(dāng)前屬性設(shè)置分別進行入棧和出棧操作昌跌。

任何可以做動畫的圖層屬性都會添加到棧頂?shù)氖聞?wù)宝剖,你可以通過 +setAnimationDuration: 方法來設(shè)置當(dāng)前事務(wù)的動畫時間,如果不進行設(shè)置,默認(rèn)的時間是0.25s包竹。

我們現(xiàn)在使用事物來完成上一個例子中的動畫,并將動畫時間延長镐侯。

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    // 開始一個動畫事務(wù)
    [CATransaction begin];
    // 設(shè)置動畫的執(zhí)行時間
    [CATransaction setAnimationDuration:1.0];
    // 生成顏色搀继,作為動畫的變化新值
    CGFloat red = arc4random() / (CGFloat)INT_MAX;
    CGFloat green = arc4random() / (CGFloat)INT_MAX;
    CGFloat blue = arc4random() / (CGFloat)INT_MAX;
    self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
    // 提交動畫事務(wù)
    [CATransaction commit];
}
顯示動畫

我們可以看到圖層的動畫效果依舊沒變,但是漸變的時間明顯變長了很多棠绘。從代碼上看件相,我們僅僅將圖層需要改變的屬性加到 +begincommit 之間再扭,并為此事務(wù)設(shè)置了一個時間。

如果你使用過 UIView 的動畫夜矗,那么應(yīng)該使用過 +beginAnimations:context:+commitAnimations泛范,實際上這兩個都是對 CATransaction 的封裝,其所做動畫都是由 CATransaction 完成的紊撕。

完成回調(diào)

CATranscation 的 API 除了提供設(shè)置動畫時間 +setAnimationDuration: 還提供了動畫完成的回調(diào)方法:+ setCompletionBlock:罢荡。你可以在該方法中接著完成一些事情。

修改一下代碼:

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    // 開始一個動畫事務(wù)
    [CATransaction begin];
    // 設(shè)置動畫的執(zhí)行時間
    [CATransaction setAnimationDuration:1.0];
    // 生成顏色逛揩,作為動畫的變化新值
    CGFloat red = arc4random() / (CGFloat)INT_MAX;
    CGFloat green = arc4random() / (CGFloat)INT_MAX;
    CGFloat blue = arc4random() / (CGFloat)INT_MAX;
    self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
    // 提交動畫事務(wù)
    [CATransaction commit];
    // 動畫完成回調(diào)柠傍,可以寫在 commit 后面
    [CATransaction setCompletionBlock:^{
        self.colorLayer.affineTransform = CGAffineTransformRotate(self.colorLayer.affineTransform, M_PI_4);
    }];
}
動畫完成回調(diào)

圖層動畫的過程

當(dāng)我們給對 CALayer 的屬性設(shè)新值時,圖層經(jīng)過以下幾個過程來檢測應(yīng)該如何呈現(xiàn)新值辩稽。

  • 圖層首先檢測它是否有委托者惧笛,并且是否實現(xiàn)了協(xié)議 CALayerDelegate 中的方法 -actionForLayer:forKey:,如果有逞泄,直接調(diào)用并返回結(jié)果患整。
  • 如果沒有委托者,或者委托沒有實現(xiàn)上述方法喷众,圖層會檢查屬性 actions 字典各谚,試圖找到對應(yīng)的屬性名。
  • 如果依舊沒有到千,圖層還是檢查屬性 style 字典昌渤,再次嘗試搜索對應(yīng)的屬性名。
  • 最后憔四,如果都未能找到膀息,那么圖層直接會調(diào)用默認(rèn)的行為 defaultActionForKey: 方法來展現(xiàn)對應(yīng)屬性的新值。

那么了赵,既然我們知道了圖層的行為過程潜支,我們是否可以以此做些什么?實際上柿汛,我們可以參與圖層的行為過程來改變隱式動畫的行為冗酿。

我們首先通過圖層的委托代理完成新的動畫過程。

@interface ViewController ()< CALayerDelegate >
@property (strong, nonatomic)CALayer* colorLayer;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.colorLayer = [CALayer layer];
    self.colorLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
    self.colorLayer.backgroundColor = [UIColor blueColor].CGColor;
    [self.view.layer addSublayer:self.colorLayer];
    // 設(shè)置圖層的委托代理
    self.colorLayer.delegate = self;
}

// 完成圖層行為協(xié)議
-(id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event{
    // 設(shè)置新的動畫
    CATransition *transition = [CATransition animation];
    transition.type = kCATransitionReveal;
    transition.subtype = kCATransitionFromLeft;
    return transition;
}

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    // 生成隨機顏色.
    CGFloat red = arc4random() / (CGFloat)INT_MAX;
    CGFloat green = arc4random() / (CGFloat)INT_MAX;
    CGFloat blue = arc4random() / (CGFloat)INT_MAX;
    self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
}
圖層行為1

我們也可以通過 actions 字典來完成:

@interface ViewController ()
<
    CALayerDelegate
>
@property (strong, nonatomic)CALayer* colorLayer;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.colorLayer = [CALayer layer];
    self.colorLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
    self.colorLayer.backgroundColor = [UIColor blueColor].CGColor;
    [self.view.layer addSublayer:self.colorLayer];
    
    // 設(shè)置 actions 字典 
    CATransition *transition = [CATransition animation];
    transition.type = kCATransitionPush;
    transition.subtype = kCATransitionFromLeft;
    transition.duration = 1.0;  // 動畫時間設(shè)置稍長
    self.colorLayer.actions = @{@"backgroundColor": transition};
}

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    // 生成隨機顏色
    CGFloat red = arc4random() / (CGFloat)INT_MAX;
    CGFloat green = arc4random() / (CGFloat)INT_MAX;
    CGFloat blue = arc4random() / (CGFloat)INT_MAX;
    self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
}
圖層行為2

顯式動畫

和隱式動畫相對的络断,顯式動畫一般是開發(fā)者們主動去實現(xiàn)的動畫效果裁替,完成圖層從舊狀態(tài)到新狀態(tài)到過渡切換。和隱式動畫不不同貌笨,顯式動畫需要開發(fā)者關(guān)心動畫從產(chǎn)生到消失的每一個細(xì)節(jié)胯究,如變化的狀態(tài)、執(zhí)行的時長躁绸、動畫的次數(shù)等等,相比系統(tǒng)提供的簡單的過渡動畫效果,顯式動畫可以完成圖層的各種各樣的酷炫效果净刮。

屬性動畫

顧名思義剥哑,屬性動畫(CAPropertyAnimation)類的是針對圖層的一些可作動畫的屬性而言的,該類不能直接拿來使用淹父,開發(fā)中通常使用其子類(這一點類似手勢)株婴,如:CABasicAnimation 經(jīng)典動畫、CAKeyframeAnimation 關(guān)鍵幀動畫暑认、CASpringAnimation 彈性動畫困介,基礎(chǔ)動畫的子類。

  • CABasicAnimation

CABasicAnimation 動畫蘸际,需要我們?yōu)槠涮峁﹥蓚€狀態(tài)值座哩,一個是初始狀態(tài)值,一個是終止?fàn)顟B(tài)值粮彤。一般來說根穷,初始值都是圖層最初的狀態(tài),當(dāng)然导坟,你也可以指定從初始狀態(tài)到非終止?fàn)顟B(tài)的之間的任意時刻屿良。

示例:

我們接著上面的例子,將圖層的圓角值做一些改變惫周。

@interface ViewController ()
@property (strong, nonatomic)CALayer* colorLayer;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.colorLayer = [CALayer layer];
    self.colorLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
    self.colorLayer.backgroundColor = [UIColor blueColor].CGColor;
    [self.view.layer addSublayer:self.colorLayer];  
}

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    // 修改圓角屬性  
    CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:@"cornerRadius"];
   animation.toValue = @(self.colorLayer.bounds.size.height/2.0);
   animation.duration = 2;
   animation.autoreverses = YES;   // 執(zhí)行逆動畫
   [self.colorLayer addAnimation:animation forKey:@"cornerRadius_animation"];
}
經(jīng)典動畫

注:animationWithKeyPath: 所帶的字符串表示需要修改的 layer 可動畫的屬性尘惧,不是隨便寫的字符串,一般常用的可動畫屬性如下:

key 說明 使用樣例
transform.scale 縮放 @(0.5)
transform.scale.x 寬的比例 @(0.5)
transform.scale.y 高的比例 @(0.5)
opacity 透明度 @(0.5)
cornerRadius 圓角的設(shè)置 @(50)
transform.rotation.x 圍繞x軸旋轉(zhuǎn) @(M_PI)
transform.rotation.y 圍繞y軸旋轉(zhuǎn) @(M_PI)
transform.rotation.z 圍繞z軸旋轉(zhuǎn) @(M_PI)
strokeStart 結(jié)合CAShapeLayer使用 賦值多變
strokeEnd 結(jié)合CAShapeLayer使用 賦值都變
bounds 大小递递,中心不變 [NSValue valueWithCGRect:CGRectMake(0, 0, 100, 100)];
position 位置(中心點的改變) [NSValue valueWithCGPoint:CGPointMake(100, 100)];
contents 內(nèi)容喷橙, 比如UIImageView的圖片 imageAnima.toValue = (id)[UIImage imageNamed:@”imageName”].CGImage;
……

動畫開始和完成事件

和隱式動畫中的完成回調(diào)不同,CAAnimation 采用了委托模式漾狼,因此你如果需要處理動畫的開始和完成事件時重慢,你需要完成 CAAnimationDelegate 的代理方法:

- (void)animationDidStart:(CAAnimation *)anim;
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;

其中 flag 標(biāo)識了動畫是否是正常結(jié)束。另外逊躁,事件傳遞不是完成block塊似踱,而是采用委托模式會帶來一個問題,就是你有多個動畫時稽煤,你需要判斷當(dāng)前是那個圖層的動畫事件核芽。

這里提供兩個用來區(qū)別的方案。一種就是在添加動畫時酵熙,-addAnimation:forKey: 設(shè)置每個動畫對應(yīng)不同的key值轧简,然后通過 animationKeys 獲取到圖層上所有的動畫key,然后對每個圖層循環(huán)所有建匾二,通過 -animationForKey: 找到結(jié)果哮独。顯然這種是非常的麻煩的方式拳芙。好在 CAAnimation 實現(xiàn)了 KVC 協(xié)議,我們可以像使用字典一樣皮璧,隨意的存取屬性舟扎。

示例:

我們將圖層在完成動畫之后,進行背景色的更改悴务。

@interface ViewController ()<CAAnimationDelegate>
@property (strong, nonatomic)CALayer* colorLayer;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.colorLayer = [CALayer layer];
    self.colorLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
    self.colorLayer.backgroundColor = [UIColor blueColor].CGColor;
    [self.view.layer addSublayer:self.colorLayer];  
}

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
   CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:@"cornerRadius"];
   animation.toValue = @(self.colorLayer.bounds.size.height/2.0);
   animation.duration = 2;
   animation.autoreverses = YES;   // 執(zhí)行逆動畫 
   animation.delegate = self;
   // 將視圖附加到動畫上  
   [animation setValue:self.colorLayer forKey:@"colorLayer"];
   [self.colorLayer addAnimation:animation forKey:@"cornerRadius_animation"];
}

// 動畫結(jié)束事件 
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
    // 通過key值睹限,取回附加的視圖 
   CALayer* layer = [anim valueForKey:@"colorLayer"];
   layer.backgroundColor = UIColor.redColor.CGColor;
}
animation通過KVC附加視圖
  • CAKeyframeAnimation

相比于經(jīng)典動畫關(guān)注于起始和終止的狀態(tài)值,關(guān)鍵幀動畫更注重整個動畫過程中多個關(guān)鍵點的狀態(tài)讯檐,因此關(guān)鍵幀動畫需要一連串的值來做動畫羡疗,你甚至可以說,經(jīng)典動畫是關(guān)鍵動畫的一種别洪。

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath:@"backgroundColor"];
    animation.duration = 4;
    animation.values = @[
                         (__bridge id)UIColor.blueColor.CGColor,
                         (__bridge id)UIColor.redColor.CGColor,
                         (__bridge id)UIColor.yellowColor.CGColor,
                         (__bridge id)UIColor.greenColor.CGColor,
                         (__bridge id)UIColor.blueColor.CGColor
                         ];
    [self.colorLayer addAnimation:animation forKey:nil];
}
關(guān)鍵幀動畫

上述例子演示了給予關(guān)鍵幀動畫的關(guān)鍵位置的數(shù)組值叨恨,實際上,關(guān)鍵幀還可以是無數(shù)個位置蕉拢,如果此時的動畫屬性是針對位置一類的特碳,我們就可以將這些關(guān)鍵幀看作是路徑,這就演變出了另一種方式做動畫晕换,即 path午乓。

下面通過移動圖層來演示這種方式的關(guān)鍵幀動畫:

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
   animation.duration = 4.0;
   // 關(guān)鍵幀路徑
   animation.path = self.path.CGPath;
   [self.imageLayer addAnimation:animation forKey:nil];
}
關(guān)鍵路徑

我們的飛船可以沿著關(guān)鍵路徑進行移動,但是我們發(fā)現(xiàn)飛船的方向一直是橫向的闸准,就如最初設(shè)置的方向益愈,而不是指向曲線切線的方向。好在蘋果發(fā)現(xiàn)了這一點夷家,并且給 CAKeyFrameAnimation 添加了一個 rotationMode 的屬性蒸其,設(shè)置它為常量 kCAAnimationRotateAuto,圖層將會根據(jù)曲線的切線自動旋轉(zhuǎn)库快。

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
   CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
   animation.duration = 4.0;
   animation.path = self.path.CGPath;
   animation.rotationMode = kCAAnimationRotateAuto;
   [self.imageLayer addAnimation:animation forKey:nil];
}
沿著切線
  • CASpringAnimation

CABasicAnimation 動畫的子類摸袁,可以實現(xiàn)彈性動畫。這個動畫是在 iOS9 之后才出現(xiàn)的义屏,UIView 有其對應(yīng)的動畫塊靠汁。

CASpringAnimation 通過幾個物理相關(guān)屬性來計算出圖層執(zhí)行的動畫效果。這些屬性如下:

mass:質(zhì)量闽铐,影響慣性蝶怔、拉伸幅度

stiffness:剛度系數(shù),剛度系數(shù)越大兄墅,形變產(chǎn)生的力就越大踢星,運動越快

damping:阻尼系數(shù),阻止彈簧伸縮的系數(shù)隙咸,阻尼系數(shù)越大沐悦,停止越快

initialVelocity:初始速率

settlingDuration:(只讀)結(jié)算時間成洗,根據(jù)當(dāng)前的動畫參數(shù)估算彈簧動畫到停止時的時間

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    CASpringAnimation *animation = [CASpringAnimation animationWithKeyPath:@"position.y"];
    animation.damping = 5;                 // 阻尼系數(shù)
    animation.stiffness = 100;             // 剛度系數(shù)
    animation.mass = 1;                    // 質(zhì)量
    animation.initialVelocity = 0;         // 初始速率
    animation.duration = animation.settlingDuration;  //結(jié)束時間
    animation.fromValue = @(self.subLayer.position.y);
    animation.toValue = @(self.subLayer.position.y+100);
    animation.removedOnCompletion = NO;
    animation.fillMode = kCAFillModeForwards;
    [self.subLayer addAnimation:animation forKey:nil];
}
彈性動畫

注:動畫并沒會改變圖層和視圖的 frame,因此在執(zhí)行完動畫后藏否,都默認(rèn)被重置到最初的位置泌枪。如果你需要動畫執(zhí)行完之后保持當(dāng)前的位置狀態(tài),可以設(shè)置 removedOnCompletion 為 NO秕岛,并設(shè)置 fillMode 模式為 kCAFillModeForwards

動畫組

之前提到的幾個屬性動畫误证,都僅僅是作用于單一屬性继薛,但是如果我們需要幾個動畫一起作用到圖層上,該怎么辦呢愈捅?蘋果為我們提供了一個組合動畫 CAAnimationGroup遏考,他是另一個繼承 CAAnimation 的子類,和幾個屬性動畫的父類同級蓝谨,它只有一個屬性 animations 數(shù)組灌具,就是用來存放多個動畫的。

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    // 動畫1
    CABasicAnimation* animation1 = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
    animation1.toValue = @(0.5);
    // 動畫2
    CABasicAnimation* animation2 = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
    animation2.toValue = @(M_PI*2);
    // 動畫組
    CAAnimationGroup* group = [CAAnimationGroup animation];
    // 設(shè)置所有的動畫的執(zhí)行時間
    group.duration = 1.5;
    group.removedOnCompletion = NO;
    group.fillMode = kCAFillModeForwards;
    // 將所有動畫都添加到組中
    group.animations = @[animation1,animation2];
    [self.subLayer addAnimation:group forKey:nil];
}
動畫組的應(yīng)用

需要注意的是譬巫,動畫組的動畫時間取所有動畫最短時間咖楣,超出時間的部分會立刻被停止,因此使用動畫組的時候芦昔,最好是一些動畫時間統(tǒng)一的組合诱贿,比如上面例子中,動畫時間并非由某個動畫來決定咕缎,而是由動畫組來設(shè)置珠十。

過渡動畫

屬性動畫只會對圖層的一些可動畫的屬性起到作用,當(dāng)我們想要改變一個不能動畫的屬性(比如圖片)凭豪,或者從層級關(guān)系中添加或者移除圖層(過場效果)焙蹭,屬性動畫將不起作用。因此嫂伞,蘋果又提供了一個用來做過渡動畫的類 CATransition孔厉,注意這個類和上面提到的事物 CATransaction 不是同一個東西。

CATransition是 CAAnimation 的子類末早,它由兩個過渡類型來控制變換效果烟馅,一個是 type:用來控制過渡效果,一個是 subtype:用來控制過渡的方向然磷。

type 的幾種類型:

kCATransitionFade       // 默認(rèn)郑趁,漸變消失
kCATransitionMoveIn     // 從當(dāng)前圖層上面劃入
kCATransitionPush       // 當(dāng)前圖層被推出,用新值替換
kCATransitionReveal     // 從當(dāng)前圖層上面劃出姿搜,效果和 kCATransitionMoveIn 相反

除了系統(tǒng)開放出來的四種類型寡润,還有幾種私有API捆憎,可以通過字符串來設(shè)置:

cube                    //立方體翻滾效果
oglFlip                 //上下左右翻轉(zhuǎn)效果
suckEffect              //收縮效果,如一塊布被抽走(不支持過渡方向)
rippleEffect            //滴水效果(不支持過渡方向)
pageCurl                //向上翻頁效果
pageUnCurl              //向下翻頁效果
cameraIrisHollowOpen    //相機鏡頭打開效果(不支持過渡方向)
cameraIrisHollowClose   //相機鏡頭關(guān)上效果(不支持過渡方向)

subtype 的幾種類型:

kCATransitionFromRight
kCATransitionFromLeft
kCATransitionFromTop
kCATransitionFromBottom

示例:

我們來使用過渡動畫來切換幾張圖片

// 獲取隨機整數(shù)
#define randomFromAtoB(A,B) (int)(A+(arc4random()%(B-A+1)))

@interface ViewController ()
{
    NSInteger currentIndex;
}
@property (strong, nonatomic) CALayer* subLayer;
@property (strong,nonatomic) NSArray *images;       // 圖片數(shù)組
@property (strong,nonatomic) NSArray *animations;   // 動畫類型數(shù)組
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self.view.layer addSublayer:self.subLayer];
}

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    CATransition *transition = [CATransition new];
    // 設(shè)置動畫類型,注意對于蘋果官方?jīng)]公開的動畫類型只能使用字符串梭纹,并沒有對應(yīng)的常量定義
    transition.type = self.animations[randomFromAtoB(0, self.animations.count-1)];
    // 設(shè)置子類型,方向
    transition.subtype = @[kCATransitionFromRight,
                           kCATransitionFromLeft,
                           kCATransitionFromTop,
                           kCATransitionFromBottom][randomFromAtoB(0, 3)];
    // 設(shè)置動畫時間
    transition.duration = 1.0;
    // 添加新的視圖
    currentIndex = (currentIndex+1)%self.images.count;
    NSString *imageName = self.images[currentIndex];
    self.subLayer.contents = (__bridge id)[UIImage imageNamed:imageName].CGImage;
    [self.subLayer addAnimation:transition forKey:@"KCATransitionAnimation"];
}

-(CALayer *)subLayer{
    if (_subLayer==nil) {
        _subLayer = [CALayer new];
        _subLayer.frame = self.view.bounds;
        _subLayer.backgroundColor = UIColor.blueColor.CGColor;
        _subLayer.contents = (__bridge id)[UIImage imageNamed:@"0.jpg"].CGImage;
    }
    return _subLayer;
}

-(NSArray *)animations{
    if (_animations == nil) {
        _animations = @[@"fade",                // 淡出效果
                        @"movein",                      // 新視圖移動到舊視圖
                        @"push",                            // 新視圖推出到舊視圖
                        @"reveal",                  // 移開舊視圖現(xiàn)實新視圖
                        @"cube",                            // 立方體翻轉(zhuǎn)效果
                        @"oglFlip",                     // 翻轉(zhuǎn)效果
                        @"suckEffect",              // 吸收效果
                        @"rippleEffect",                // 水滴效果
                        @"pageCurl",                    // 向上翻頁
                        @"pageUnCurl",                  // 向下翻頁
                        @"cameralIrisHollowOpen",       // 攝像頭打開
                        @"cameraIrisHollowClose",       // 攝像頭關(guān)閉
                        ];
    }
    return _animations;
}

-(NSArray *)images{
    if (_images==nil) {
        _images = @[@"0.jpg",
                    @"1.jpg",
                    @"2.jpg",
                    @"3.jpg",
                    @"4.jpg",
                    @"5.jpg"];
    }
    return _images;
}
過渡動畫

CATransition 并不作用于指定的圖層屬性躲惰,這就是說你可以在即使不能準(zhǔn)確得知改變了什么的情況下對圖層做動畫,例如变抽,在不知道 UITableView 哪一行被添加或者刪除的情況下础拨,直接就可以平滑地刷新它,又如在 UITabBarController 切換視圖時添加上過渡動畫绍载,可以比如淡入淡出的效果诡宗,又或者在不知道 UIViewController 內(nèi)部的視圖層級的情況下對兩個不同的實例做過渡動畫。

這些例子和我們之前所討論的情況完全不同击儡,因為它們不僅涉及到圖層的屬性塔沃,而且是整個圖層樹的改變--我們在這種動畫的過程中手動在層級關(guān)系中添加或者移除圖層。

自定義過渡動畫

過渡動畫做基礎(chǔ)的原則就是對原始的圖層外觀截圖阳谍,然后添加一段動畫蛀柴,平滑過渡到圖層改變之后那個截圖的效果。如果我們對圖層截圖矫夯,就可以使用屬性動畫來代替 CATransition 或者是 UIKit 的過渡方法來實現(xiàn)動畫鸽疾。

CALayer 有一個 -renderInContext: 方法,可以通過把它繪制到 Core Graphics 的上下文中捕獲當(dāng)前內(nèi)容的圖片茧痒,然后在另外的視圖中顯示出來肮韧。如果我們把這個截屏視圖置于原始視圖之上,就可以遮住真實視圖的所有變化旺订,于是重新創(chuàng)建了一個簡單的過渡效果弄企。

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    // 獲取當(dāng)前屏幕的截圖
    UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, YES, 0.0);
    [self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *coverImage = UIGraphicsGetImageFromCurrentImageContext();
    //insert snapshot view in front of this one
    UIView *coverView = [[UIImageView alloc] initWithImage:coverImage];
    coverView.frame = self.view.bounds;
    // 將截圖覆蓋到當(dāng)前視圖上
    [self.view addSubview:coverView];
    // 為了演示過渡效果,我們修改一下當(dāng)前視圖的背景色区拳,以區(qū)分之前的視圖
    CGFloat red = arc4random() / (CGFloat)INT_MAX;
    CGFloat green = arc4random() / (CGFloat)INT_MAX;
    CGFloat blue = arc4random() / (CGFloat)INT_MAX;
    self.view.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
    // 執(zhí)行過渡動畫
    [UIView animateWithDuration:0.75 animations:^{
        CGAffineTransform transform = CGAffineTransformMakeScale(0.01, 1);
        coverView.transform = transform;
    } completion:^(BOOL finished) {
        // 最后移除掉障眼法的圖層
        [coverView removeFromSuperview];
    }];
}
自定義的一種過渡動畫

取消動畫

在我們使用-addAnimation:forKey:方法中的key參數(shù)來在添加動畫之后檢索一個動畫拘领,使用如下方法:

- (CAAnimation *)animationForKey:(NSString *)key;

但并不支持在動畫運行過程中修改動畫,所以這個方法主要用來檢測動畫的屬性樱调,或者判斷它是否被添加到當(dāng)前圖層中约素。

為了終止一個指定的動畫,你可以用如下方法把它從圖層移除掉:

- (void)removeAnimationForKey:(NSString *)key;

也可以根據(jù)需要移除所有動畫:

- (void)removeAllAnimations;

動畫一旦被移除笆凌,圖層的外觀就立刻更新到當(dāng)前的模型圖層的值圣猎。一般說來,動畫在結(jié)束之后被自動移除乞而,除非設(shè)置 removedOnCompletion 為NO送悔,如果你設(shè)置動畫在結(jié)束之后不被自動移除,那么當(dāng)它不需要的時候你要手動移除它;否則它會一直存在于內(nèi)存中欠啤,直到圖層被銷毀荚藻。

時間相關(guān)

CAMediaTiming 協(xié)議

CAMediaTiming 協(xié)議定義了在一段動畫內(nèi)用來控制逝去時間的屬性的集合, CALayer 和 CAAnimation 都實現(xiàn)了這個協(xié)議洁段,所以時間可以被任意基于一個圖層或者一段動畫的類控制应狱。

CAAnimation 中幾個常用的屬性:

duration

duration(CAMediaTiming的屬性之一),duration是一個 CFTimeInterval 的類型(類似于 NSTimeInterval 的一種雙精度浮點類型)祠丝,對將要進行的動畫的一次迭代指定了時間疾呻。

repeatCount

代表動畫重復(fù)的迭代次數(shù)。

repeatDuration

它讓動畫重復(fù)一個指定的時間写半,而不是指定次數(shù)罐韩。

autoreverses

在每次間隔交替循環(huán)過程中自動回放。在設(shè)置此值為 YES 時污朽,duration 的一半時間會用來做自動回放。

相對時間的幾個屬性:

在 Core Animation 中龙考,時間都是相對的蟆肆,每個動畫都有它自己描述的時間,可以獨立地加速晦款,延時或者偏移炎功。

beginTime

指定了動畫開始之前的的延遲時間。這里的延遲從動畫添加到可見圖層的那一刻開始測量缓溅,默認(rèn)是0(就是說動畫會立刻執(zhí)行)蛇损。

speed

是一個時間的倍數(shù),默認(rèn)1.0坛怪,減少它會減慢圖層/動畫的時間淤齐,增加它會加快速度。如果2.0的速度袜匿,那么對于一個 duration 為1的動畫更啄,實際上在0.5秒的時候就已經(jīng)完成了。

特別的居灯,前面提到到 CALayer 也實現(xiàn)了 CAMediaTiming 協(xié)議祭务,如果把圖層的 speed 的值設(shè)置為0,它會暫停任何添加到圖層上的動畫怪嫌,如果 speed 的值大于1.0則變現(xiàn)為快進义锥,如果設(shè)置成一個負(fù)值則變?yōu)榈够氐膭赢嫛?/p>

如果設(shè)置主 window 圖層的 speed 為0時,可以將整個應(yīng)用程序的動畫暫停岩灭。同樣的拌倍,如果我們將其設(shè)置為快進,就可以完成加速多有的視圖動畫來進行自動化測試川背。設(shè)置代碼如下:

self.window.layer.speed = 100;

timeOffset

和 beginTime 類似贰拿,但是和增加 beginTime 導(dǎo)致的延遲動畫不同蛤袒,增加 timeOffset 只是讓動畫快進到某一點,例如膨更,對于一個持續(xù)1秒的動畫來說妙真,設(shè)置 timeOffset 為0.5意味著動畫將從一半的地方開始。

timeOffset 一個很有用的功能在于你可以它可以讓你手動控制動畫進程荚守,通過設(shè)置 speed 為0珍德,可以禁用動畫的自動播放,然后來使用 timeOffset 來來回顯示動畫序列矗漾。這可以使得運用手勢來手動控制復(fù)雜動畫或者多個圖層的動畫組變得很簡單锈候。

動畫結(jié)束后的填充模式:

fillMode

kCAFillModeForwards 
kCAFillModeBackwards 
kCAFillModeBoth 
kCAFillModeRemoved

這個屬性表示動畫結(jié)束之后,是保持動畫最開始的那一幀還是保持動畫結(jié)束之后的那一幀敞贡。默認(rèn)情況是kCAFillModeRemoved泵琳。

緩沖過渡

CAMediaTimingFunction

動畫時間決定了圖層變換的時長,而動畫的速度表示動畫執(zhí)行的“速率”誊役,通常是變化量和時間的比值获列。這里的變化量可以是圖層移動的距離,縮放的大小蛔垢,也可以是圖層的透明度击孩、填充色等。實際上鹏漆,任意的可以做動畫的屬性的變化差值都可以稱作變化量巩梢。

默認(rèn)情況下,我們的動畫都是線性變化的艺玲,即速率是恒定不變的括蝠,就如前面的那些示例,但是有時候饭聚,我們并不希望動畫的速度一層不變又跛,那么該怎么做呢?幸運的事若治,Core Animation 已經(jīng)為我們設(shè)計了一系列標(biāo)準(zhǔn)函數(shù)提供給我們使用慨蓝。

timingFunction:

CAAnimation 的 timingFunction 屬性,是 CAMediaTimingFunction 類的一個對象端幼。(如果想改變隱式動畫的計時函數(shù)礼烈,同樣也可以使用CATransaction的+setAnimationTimingFunction:方法)

我們可以通過下面的構(gòu)造函數(shù)來創(chuàng)建緩沖對象:

+ (instancetype)functionWithName:(CAMediaTimingFunctionName)name;

傳入如下幾個常量之一:

kCAMediaTimingFunctionLinear    // 線性
kCAMediaTimingFunctionEaseIn        // 緩慢起步
kCAMediaTimingFunctionEaseOut   // 緩慢停止
kCAMediaTimingFunctionEaseInEaseOut // 先慢起步后快最后慢停止
kCAMediaTimingFunctionDefault       // 類似于上面

示例:

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:@"position.x"];
    animation.duration = 2.0;
    animation.toValue = @(self.subLayer.frame.origin.y+400);
    animation.removedOnCompletion = NO;
    animation.fillMode = kCAFillModeForwards;
    animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    [self.subLayer addAnimation:animation forKey:nil];
}
幾種緩沖對比

上圖的緩沖模式分別為:

kCAMediaTimingFunctionLinear    
kCAMediaTimingFunctionEaseIn        
kCAMediaTimingFunctionEaseOut   
kCAMediaTimingFunctionEaseInEaseOut 

CAKeyframeAnimation 有一個NSArray類型的 timingFunctions 屬性,我們可以用它來對每次動畫的步驟指定不同的計時函數(shù)婆跑。但是指定函數(shù)的個數(shù)一定要等于 keyframes 數(shù)組的元素個數(shù)減一此熬,因為它是描述每一幀之間動畫速度的函數(shù)。

自定義緩沖函數(shù)

在上一節(jié)中,介紹了幾個系統(tǒng)為我們定義好的緩沖函數(shù)犀忱,能適用于大部分的應(yīng)用環(huán)境募谎。我們注意到,除了 +functionWithName: 之外阴汇,CAMediaTimingFunction 同樣有另一個構(gòu)造函數(shù)数冬,一個有四個浮點參數(shù)的 +functionWithControlPoints::::,使用這個方法搀庶,我們可以創(chuàng)建一個自定義的緩沖函數(shù)拐纱,來匹配我們的動畫。

CAMediaTimingFunction 函數(shù)的主要原則在于它把輸入的時間轉(zhuǎn)換成起點和終點之間成比例的改變哥倔。我們可以用一個簡單的圖標(biāo)來解釋秸架,橫軸代表時間,縱軸代表改變的量咆蒿,于是線性的緩沖就是一條從起點開始的簡單的斜線东抹。

線性緩沖函數(shù)的圖像

這條曲線的斜率代表了速度,斜率的改變代表了加速度沃测,原則上來說府阀,任何加速的曲線都可以用這種圖像來表示,但是 CAMediaTimingFunction 使用了一個叫做三次貝塞爾曲線的函數(shù)芽突,它只可以產(chǎn)出指定緩沖函數(shù)的子集。

三次貝塞爾緩沖函數(shù)

三次貝塞爾緩沖函數(shù)表達出先加速董瞻,然后減速寞蚌,最后快到達終點的時候又加速的情況,那么標(biāo)準(zhǔn)的緩沖函數(shù)又該如何用圖像來表示呢钠糊?

CAMediaTimingFunction 有一個叫做-getControlPointAtIndex:values:的方法挟秤,可以用來檢索曲線的點,使用它我們可以找到標(biāo)準(zhǔn)緩沖函數(shù)的點抄伍,然后用 UIBezierPath 和 CAShapeLayer 來把它畫出來艘刚。

曲線的起始和終點始終是{0, 0}和{1, 1},于是我們只需要檢索曲線的第二個和第三個點(控制點)截珍。

- (void)viewDidLoad{
    [super viewDidLoad];
    CAMediaTimingFunction *function = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut];
    // 獲取到兩個控制點
    CGPoint controlPoint1, controlPoint2;
    [function getControlPointAtIndex:1 values:(float *)&controlPoint1];
    [function getControlPointAtIndex:2 values:(float *)&controlPoint2];
    // 創(chuàng)建曲線
    UIBezierPath *path = [[UIBezierPath alloc] init];
    [path moveToPoint:CGPointZero];
    [path addCurveToPoint:CGPointMake(1, 1)
            controlPoint1:controlPoint1 controlPoint2:controlPoint2];
    // 轉(zhuǎn)換點攀甚,讓其可見
    [path applyTransform:CGAffineTransformMakeScale(200, 200)];
    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    shapeLayer.strokeColor = [UIColor redColor].CGColor;
    shapeLayer.fillColor = [UIColor clearColor].CGColor;
    shapeLayer.lineWidth = 4.0f;
    shapeLayer.path = path.CGPath;
    [self.layerView.layer addSublayer:shapeLayer];
    self.layerView.layer.geometryFlipped = YES;
}

所有的標(biāo)準(zhǔn)緩沖函數(shù)的圖像如下:

標(biāo)準(zhǔn)CAMediaTimingFunction緩沖曲線

相關(guān)閱讀

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市岗喉,隨后出現(xiàn)的幾起案子秋度,更是在濱河造成了極大的恐慌,老刑警劉巖钱床,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件荚斯,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機事期,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門滥壕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人兽泣,你說我怎么就攤上這事绎橘。” “怎么了撞叨?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵金踪,是天一觀的道長。 經(jīng)常有香客問我牵敷,道長胡岔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任枷餐,我火速辦了婚禮靶瘸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘毛肋。我一直安慰自己怨咪,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布润匙。 她就那樣靜靜地躺著诗眨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪孕讳。 梳的紋絲不亂的頭發(fā)上匠楚,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天,我揣著相機與錄音厂财,去河邊找鬼芋簿。 笑死,一個胖子當(dāng)著我的面吹牛璃饱,可吹牛的內(nèi)容都是我干的与斤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼荚恶,長吁一口氣:“原來是場噩夢啊……” “哼撩穿!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起谒撼,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤冗锁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后嗤栓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冻河,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡箍邮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了叨叙。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锭弊。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖擂错,靈堂內(nèi)的尸體忽然破棺而出味滞,到底是詐尸還是另有隱情,我是刑警寧澤钮呀,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布剑鞍,位于F島的核電站,受9級特大地震影響爽醋,放射性物質(zhì)發(fā)生泄漏蚁署。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一蚂四、第九天 我趴在偏房一處隱蔽的房頂上張望光戈。 院中可真熱鬧,春花似錦遂赠、人聲如沸久妆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽筷弦。三九已至,卻和暖如春抑诸,著一層夾襖步出監(jiān)牢的瞬間烂琴,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工哼鬓, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人边灭。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓异希,卻偏偏與公主長得像,于是被迫代替她去往敵國和親绒瘦。 傳聞我的和親對象是個殘疾皇子称簿,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,941評論 2 355

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