iOS開(kāi)發(fā)讀書(shū)筆記:iOS Core Animation:Advanced Techniques(iOS核心動(dòng)畫高級(jí)技巧)-中篇

iOS Core Animation:Advanced Techniques(iOS核心動(dòng)畫高級(jí)技巧)-上篇
iOS Core Animation:Advanced Techniques(iOS核心動(dòng)畫高級(jí)技巧)-中篇
iOS Core Animation:Advanced Techniques(iOS核心動(dòng)畫高級(jí)技巧)-下篇

目錄

  1. 專用圖層
    6.1 CAShapeLayer
    6.2 CATextLayer
    6.3 CATransformLayer
    6.4 CAGradientLayer
    6.5 CAReplicatorLayer
    6.6 CAScrollLayer
    6.7 CATiledLayer
    6.8 CAEmitterLayer
    6.9 CAEAGLLayer
    6.10 AVPlayerLayer
  2. 隱式動(dòng)畫
    7.1 事務(wù)
    7.2 完成塊
    7.3 圖層行為
    7.4 呈現(xiàn)與模型
  3. 顯示動(dòng)畫
    8.1 屬性動(dòng)畫
    8.2 動(dòng)畫組
    8.3 過(guò)渡
    8.4 在動(dòng)畫過(guò)程中取消動(dòng)畫
  4. 圖層時(shí)間
    9.1 CAMediaTiming協(xié)議
    9.2 層級(jí)關(guān)系時(shí)間
    9.3 手動(dòng)動(dòng)畫
  5. 緩沖
    10.1 動(dòng)畫速度
    10.2 自定義緩沖函數(shù)

第六章:專用圖層

前言:

到目前為止,我們已經(jīng)探討過(guò)CALayer類了,同時(shí)我們也了解到了一些非常有用的繪圖和動(dòng)畫功能。但是Core Animation圖層不僅僅能作用于圖片和顏色而已。本章就會(huì)學(xué)習(xí)其他的一些圖層類,進(jìn)一步擴(kuò)展使用Core Animation繪圖的能力。

第1篇:CAShapeLayer

CAShapeLayer是一個(gè)通過(guò)矢量圖形而不是bitmap來(lái)繪制的圖層子類。你指定諸如顏色和線寬等屬性,用CGPath來(lái)定義想要繪制的圖形,最后CAShapeLayer就自動(dòng)渲染出來(lái)了。當(dāng)然,你也可以用Core Graphics直接向原始的CALyer的內(nèi)容中繪制一個(gè)路徑,相比直下,使用CAShapeLayer有以下一些優(yōu)點(diǎn):

  • 渲染快速。CAShapeLayer使用了硬件加速,繪制同一圖形會(huì)比用Core Graphics快很多。
  • 高效使用內(nèi)存。一個(gè)CAShapeLayer不需要像普通CALayer一樣創(chuàng)建一個(gè)寄宿圖形,所以無(wú)論有多大,都不會(huì)占用太多的內(nèi)存。
  • 不會(huì)被圖層邊界剪裁掉。一個(gè)CAShapeLayer可以在邊界之外繪制。你的圖層路徑不會(huì)像在使用Core Graphics的普通CALayer一樣被剪裁掉(如我們?cè)诘诙滤?jiàn))。
  • 不會(huì)出現(xiàn)像素化。當(dāng)你給CAShapeLayer做3D變換時(shí),它不像一個(gè)有寄宿圖的普通圖層一樣變得像素化。

創(chuàng)建一個(gè)CGPath

CAShapeLayer可以用來(lái)繪制所有能夠通過(guò)CGPath來(lái)表示的形狀。這個(gè)形狀不一定要閉合,圖層路徑也不一定要不可破,事實(shí)上你可以在一個(gè)圖層上繪制好幾個(gè)不同的形狀。你可以控制一些屬性比如lineWith(線寬,用點(diǎn)表示單位),lineCap(線條結(jié)尾的樣子),和lineJoin(線條之間的結(jié)合點(diǎn)的樣子);但是在圖層層面你只有一次機(jī)會(huì)設(shè)置這些屬性。如果你想用不同顏色或風(fēng)格來(lái)繪制多個(gè)形狀,就不得不為每個(gè)形狀準(zhǔn)備一個(gè)圖層了。

下面的代碼用一個(gè)CAShapeLayer渲染一個(gè)簡(jiǎn)單的火柴人。CAShapeLayer屬性是CGPathRef類型,但是我們用UIBezierPath幫助類創(chuàng)建了圖層路徑,這樣我們就不用考慮人工釋放CGPath了。

  UIBezierPath *path = [[UIBezierPath alloc] init];
  [path moveToPoint:CGPointMake(175, 100)];
  [path addArcWithCenter:CGPointMake(150, 100) radius:25 startAngle:0 endAngle:2*M_PI clockwise:YES];
  [path moveToPoint:CGPointMake(150, 125)];
  [path addLineToPoint:CGPointMake(150, 175)];
  [path addLineToPoint:CGPointMake(125, 225)];
  [path moveToPoint:CGPointMake(150, 175)];
  [path addLineToPoint:CGPointMake(175, 225)];
  [path moveToPoint:CGPointMake(100, 150)];
  [path addLineToPoint:CGPointMake(200, 150)];

  //create shape layer
  CAShapeLayer *shapeLayer = [CAShapeLayer layer];
  shapeLayer.strokeColor = [UIColor redColor].CGColor;
  shapeLayer.fillColor = [UIColor clearColor].CGColor;
  shapeLayer.lineWidth = 5;
  shapeLayer.lineJoin = kCALineJoinRound;
  shapeLayer.lineCap = kCALineCapRound;
  shapeLayer.path = path.CGPath;
  //add it to our view
  [self.containerView.layer addSublayer:shapeLayer];

圓角

下面這段代碼使用UIBezierPath和CAShapeLayer繪制了一個(gè)有三個(gè)圓角一個(gè)直角的矩形:

//define path parameters
CGRect rect = CGRectMake(50, 50, 100, 100);
CGSize radii = CGSizeMake(20, 20);
UIRectCorner corners = UIRectCornerTopRight | UIRectCornerBottomRight | UIRectCornerBottomLeft;
//create path
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:corners cornerRadii:radii];

我們可以通過(guò)這個(gè)圖層路徑繪制一個(gè)既有直角又有圓角的視圖。如果我們想依照此圖形來(lái)剪裁視圖內(nèi)容,我們可以把CAShapeLayer作為視圖的宿主圖層,而不是添加一個(gè)子視圖(圖層蒙板的詳細(xì)解釋見(jiàn)第四章『視覺(jué)效果』)。

第2篇:CATextLayer

如果你想在一個(gè)圖層里面顯示文字,完全可以借助圖層代理直接將字符串使用Core Graphics寫入圖層的內(nèi)容(這就是UILabel的精髓)。
Core Animation提供了一個(gè)CALayer的子類CATextLayer,它以圖層的形式包含了UILabel幾乎所有的繪制特性,并且額外提供了一些新的特性。
同樣,CATextLayer也要比UILabel渲染得快得多。很少有人知道在iOS 6及之前的版本,UILabel其實(shí)是通過(guò)WebKit來(lái)實(shí)現(xiàn)繪制的,這樣就造成了當(dāng)有很多文字的時(shí)候就會(huì)有極大的性能壓力。而CATextLayer使用了Core text,并且渲染得非常快。
用CATextLayer來(lái)實(shí)現(xiàn)一個(gè)UILabel

  //create a text layer
  CATextLayer *textLayer = [CATextLayer layer];
  textLayer.frame = self.labelView.bounds;
  [self.labelView.layer addSublayer:textLayer];

  //set text attributes
  textLayer.foregroundColor = [UIColor blackColor].CGColor;
  textLayer.alignmentMode = kCAAlignmentJustified;
  textLayer.wrapped = YES;

  //choose a font
  UIFont *font = [UIFont systemFontOfSize:15];

  //set layer font
  CFStringRef fontName = (__bridge CFStringRef)font.fontName;
  CGFontRef fontRef = CGFontCreateWithFontName(fontName);
  textLayer.font = fontRef;
  textLayer.fontSize = font.pointSize;
  CGFontRelease(fontRef);

  //choose some text
  NSString *text = @"Lorem ipsum dolor sit amet, consectetur adipiscing \ elit. Quisque massa arcu, eleifend vel varius in, facilisis pulvinar \ leo. Nunc quis nunc at mauris pharetra condimentum ut ac neque. Nunc elementum, libero ut porttitor dictum, diam odio congue lacus, vel \ fringilla sapien diam at purus. Etiam suscipit pretium nunc sit amet \ lobortis";

  //set layer text
  textLayer.string = text;

如果你實(shí)現(xiàn)了代碼并且界面顯示出來(lái)了,你會(huì)發(fā)現(xiàn)一個(gè)奇怪的地方:這些文本有一些像素化了。這是因?yàn)椴](méi)有以Retina的方式渲染,第二章提到了這個(gè)contentScale屬性,用來(lái)決定圖層內(nèi)容應(yīng)該以怎樣的分辨率來(lái)渲染。contentsScale并不關(guān)心屏幕的拉伸因素而總是默認(rèn)為1.0。如果我們想以Retina的質(zhì)量來(lái)顯示文字,我們就得手動(dòng)地設(shè)置CATextLayer的contentsScale屬性,如下:

  textLayer.contentsScale = [UIScreen mainScreen].scale;

注意:CATextLayer的string屬性并不是你想象的NSString類型,而是id類型。這樣你既可以用NSString也可以用NSAttributedString來(lái)指定文本了(注意,NSAttributedString并不是NSString的子類)。屬性化字符串是iOS用來(lái)渲染字體風(fēng)格的機(jī)制,它以特定的方式來(lái)決定指定范圍內(nèi)的字符串的原始信息,比如字體,顏色,字重,斜體等。

富文本

用CATextLayer實(shí)現(xiàn)一個(gè)富文本標(biāo)簽。

UILabel的替代品

我們想要一個(gè)用CATextLayer作為宿主圖層的UILabel子類,這樣就可以沒(méi)有冗余的寄宿圖啦。

就像我們?cè)诘谝徽隆簣D層樹(shù)』討論的一樣,每一個(gè)UIView都是寄宿在一個(gè)CALayer的示例上。這個(gè)圖層是由視圖自動(dòng)創(chuàng)建和管理的,那我們可以用別的圖層類型替代它么?一旦被創(chuàng)建,我們就無(wú)法代替這個(gè)圖層了。但是如果我們繼承了UIView,那我們就可以重寫+layerClass方法使得在創(chuàng)建的時(shí)候能返回一個(gè)不同的圖層子類。UIView會(huì)在初始化的時(shí)候調(diào)用+layerClass方法,然后用它的返回類型來(lái)創(chuàng)建宿主圖層。
下面代碼演示了一個(gè)UILabel子類LayerLabel用CATextLayer繪制它的問(wèn)題,而不是調(diào)用一般的UILabel使用的較慢的-drawRect:方法

#import "LayerLabel.h"
@implementation LayerLabel
+ (Class)layerClass {
  //this makes our label create a CATextLayer //instead of a regular CALayer for its backing layer
  return [CATextLayer class];
}

- (CATextLayer *)textLayer {
  return (CATextLayer *)self.layer;
}

- (void)setUp {
  //set defaults from UILabel settings
  self.text = self.text;
  self.textColor = self.textColor;
  self.font = self.font;

  //we should really derive these from the UILabel settings too
  //but that's complicated, so for now we'll just hard-code them
  [self textLayer].alignmentMode = kCAAlignmentJustified;
  
  [self textLayer].wrapped = YES;
  [self.layer display];
}

- (id)initWithFrame:(CGRect)frame {
  //called when creating label programmatically
  if (self = [super initWithFrame:frame]) {
    [self setUp];
  }
  return self;
}

- (void)awakeFromNib {
  //called when creating label using Interface Builder
  [self setUp];
}

- (void)setText:(NSString *)text {
  super.text = text;
  //set layer text
  [self textLayer].string = text;
}

- (void)setTextColor:(UIColor *)textColor {
  super.textColor = textColor;
  //set layer text color
  [self textLayer].foregroundColor = textColor.CGColor;
}

- (void)setFont:(UIFont *)font {
  super.font = font;
  //set layer font
  CFStringRef fontName = (__bridge CFStringRef)font.fontName;
  CGFontRef fontRef = CGFontCreateWithFontName(fontName);
  [self textLayer].font = fontRef;
  [self textLayer].fontSize = font.pointSize;
  CGFontRelease(fontRef);
}
@end

如果你運(yùn)行代碼,你會(huì)發(fā)現(xiàn)文本并沒(méi)有像素化,而我們也沒(méi)有設(shè)置contentsScale屬性。把CATextLayer作為宿主圖層的另一好處就是視圖自動(dòng)設(shè)置了contentsScale屬性。
總得來(lái)說(shuō),如果想在app里面充分利用CALayer子類,用+layerClass來(lái)創(chuàng)建基于不同圖層的視圖是一個(gè)簡(jiǎn)單可復(fù)用的方法。

第3篇:CATransformLayer

CATransformLayer不同于普通的CALayer,因?yàn)樗荒茱@示它自己的內(nèi)容。只有當(dāng)存在了一個(gè)能作用域子圖層的變換它才真正存在。CATransformLayer并不平面化它的子圖層,所以它能夠用于構(gòu)造一個(gè)層級(jí)的3D結(jié)構(gòu)

第4篇:CAGradientLayer

CAGradientLayer是用來(lái)生成兩種或更多顏色平滑漸變的。用Core Graphics復(fù)制一個(gè)CAGradientLayer并將內(nèi)容繪制到一個(gè)普通圖層的寄宿圖也是有可能的,但是CAGradientLayer的真正好處在于繪制使用了硬件加速。

基礎(chǔ)漸變:

用CAGradientLayer實(shí)現(xiàn)簡(jiǎn)單的兩種顏色的對(duì)角線漸變

  //create gradient layer and add it to our container view
  CAGradientLayer *gradientLayer = [CAGradientLayer layer];
  gradientLayer.frame = self.containerView.bounds;
  [self.containerView.layer addSublayer:gradientLayer];

  //set gradient colors
  gradientLayer.colors = @[(__bridge id)[UIColor redColor].CGColor, (__bridge id)[UIColor blueColor].CGColor];

  //set gradient start and end points
  gradientLayer.startPoint = CGPointMake(0, 0);
  gradientLayer.endPoint = CGPointMake(1, 1);

多重漸變:

可以用locations屬性來(lái)調(diào)整空間。locations屬性是一個(gè)浮點(diǎn)數(shù)值的數(shù)組(以NSNumber包裝)。這些浮點(diǎn)數(shù)定義了colors屬性中每個(gè)不同顏色的位置,同樣的,也是以單位坐標(biāo)系進(jìn)行標(biāo)定。0.0代表著漸變的開(kāi)始,1.0代表著結(jié)束。
locations數(shù)組并不是強(qiáng)制要求的,但是如果你給它賦值了就一定要確保locations的數(shù)組大小和colors數(shù)組大小一定要相同,否則你將會(huì)得到一個(gè)空白的漸變。
用locations構(gòu)造偏移至左上角的三色漸變

  //create gradient layer and add it to our container view
  CAGradientLayer *gradientLayer = [CAGradientLayer layer];
  gradientLayer.frame = self.containerView.bounds;
  [self.containerView.layer addSublayer:gradientLayer];

  //set gradient colors
  gradientLayer.colors = @[(__bridge id)[UIColor redColor].CGColor, (__bridge id) [UIColor yellowColor].CGColor, (__bridge id)[UIColor greenColor].CGColor];

  //set locations
  gradientLayer.locations = @[@0.0, @0.25, @0.5];

  //set gradient start and end points
  gradientLayer.startPoint = CGPointMake(0, 0);
  gradientLayer.endPoint = CGPointMake(1, 1);

第5篇:CAReplicatorLayer

CAReplicatorLayer的目的是為了高效生成許多相似的圖層。它會(huì)繪制一個(gè)或多個(gè)圖層的子圖層,并在每個(gè)復(fù)制體上應(yīng)用不同的變換。

重復(fù)圖層(Repeating Layers)

第6篇:CAScrollLayer

第7篇:CATiledLayer

第8篇:CAEmitterLayer

第9篇:CAEAGLLayer

第10篇:AVPlayerLayer

總結(jié):

這一章我們簡(jiǎn)要概述了一些專用圖層以及用他們實(shí)現(xiàn)的一些效果,我們只是了解到這些圖層的皮毛,像CATiledLayer和CAEMitterLayer這些類可以單獨(dú)寫一章的。但是,重點(diǎn)是記住CALayer是用處很大的,而且它并沒(méi)有為所有可能的場(chǎng)景進(jìn)行優(yōu)化。為了獲得Core Animation最好的性能,你需要為你的工作選對(duì)正確的工具,希望你能夠挖掘這些不同的CALayer子類的功能。 這一章我們通過(guò)CAEmitterLayer和AVPlayerLayer類簡(jiǎn)單地接觸到了一些動(dòng)畫,在第二章,我們將繼續(xù)深入研究動(dòng)畫,就從隱式動(dòng)畫開(kāi)始。

第七章:隱式動(dòng)畫

前言:

我們?cè)诘谝徊糠钟懻摿薈ore Animation除了動(dòng)畫之外可以做到的任何事情。但是動(dòng)畫是Core Animation庫(kù)一個(gè)非常顯著的特性。這一章我們來(lái)看看它是怎么做到的。具體來(lái)說(shuō),我們先來(lái)討論框架自動(dòng)完成的隱式動(dòng)畫(除非你明確禁用了這個(gè)功能)。

第1篇:事務(wù)

Core Animation基于一個(gè)假設(shè),說(shuō)屏幕上的任何東西都可以(或者可能)做動(dòng)畫。動(dòng)畫并不需要你在Core Animation中手動(dòng)打開(kāi),相反需要明確地關(guān)閉,否則他會(huì)一直存在。
當(dāng)你改變CALayer的一個(gè)可做動(dòng)畫的屬性,它并不能立刻在屏幕上體現(xiàn)出來(lái)。相反,它是從先前的值平滑過(guò)渡到新的值。這一切都是默認(rèn)的行為,你不需要做額外的操作。
這其實(shí)就是所謂的隱式動(dòng)畫。之所以叫隱式是因?yàn)槲覀儾](méi)有指定任何動(dòng)畫的類型 。我們僅僅改變了一個(gè)屬性,然后Core Animation來(lái)決定如何并且何時(shí)去做動(dòng)畫。Core Animaiton同樣支持顯式動(dòng)畫,下章詳細(xì)說(shuō)明。
但當(dāng)你改變一個(gè)屬性,Core Animation是如何判斷動(dòng)畫類型和持續(xù)時(shí)間的呢?實(shí)際上動(dòng)畫執(zhí)行的時(shí)間取決于當(dāng)前事務(wù)的設(shè)置,動(dòng)畫類型取決于圖層行為。

事務(wù)實(shí)際上是Core Animation用來(lái)包含一系列屬性動(dòng)畫集合的機(jī)制,任何用指定事務(wù)去改變可以做動(dòng)畫的圖層屬性都不會(huì)立刻發(fā)生變化,而是當(dāng)事務(wù)一旦提交的時(shí)候開(kāi)始用一個(gè)動(dòng)畫過(guò)渡到新值。
事務(wù)是通過(guò)CATransaction類來(lái)做管理,CATransaction沒(méi)有屬性或者實(shí)例方法,并且也不能用+alloc和-init方法創(chuàng)建它。但是可以用+begin和+commit分別來(lái)入棧或者出棧。
任何可以做動(dòng)畫的圖層屬性都會(huì)被添加到棧頂?shù)氖聞?wù),你可以通過(guò)+setAnimationDuration:方法設(shè)置當(dāng)前事務(wù)的動(dòng)畫時(shí)間,或者通過(guò)+animationDuration方法來(lái)獲取值(默認(rèn)0.25秒)。
Core Animation在每個(gè)run loop周期中自動(dòng)開(kāi)始一次新的事務(wù)(run loop是iOS負(fù)責(zé)收集用戶輸入,處理定時(shí)器或者網(wǎng)絡(luò)事件并且重新繪制屏幕的東西),即使你不顯式的用[CATransaction begin]開(kāi)始一次事務(wù),任何在一次run loop循環(huán)中屬性的改變都會(huì)被集中起來(lái),然后做一次0.25秒的動(dòng)畫。
使用CATransaction控制動(dòng)畫時(shí)間

- (IBAction)changeColor {
    //begin a new transaction
    [CATransaction begin];
    //set the animation duration to 1 second
    [CATransaction setAnimationDuration:1.0];
    //randomize the layer background color
    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;
    //commit the transaction
    [CATransaction commit];
}

如果你用過(guò)UIView的動(dòng)畫方法做過(guò)一些動(dòng)畫效果,那么應(yīng)該對(duì)這個(gè)模式不陌生。UIView有兩個(gè)方法,+beginAnimations:context:和+commitAnimations,和CATransaction的+begin和+commit方法類似。實(shí)際上在+beginAnimations:context:和+commitAnimations之間所有視圖或者圖層屬性的改變而做的動(dòng)畫都是由于設(shè)置了CATransaction的原因。

在iOS4中,蘋果對(duì)UIView添加了一種基于block的動(dòng)畫方法:+animateWithDuration:animations:。這樣寫對(duì)做一堆的屬性動(dòng)畫在語(yǔ)法上會(huì)更加簡(jiǎn)單,但實(shí)質(zhì)上它們都是在做同樣的事情。

CATransaction的+begin和+commit方法在+animateWithDuration:animations:內(nèi)部自動(dòng)調(diào)用,這樣block中所有屬性的改變都會(huì)被事務(wù)所包含。這樣也可以避免開(kāi)發(fā)者由于對(duì)+begin和+commit匹配的失誤造成的風(fēng)險(xiǎn)。

第2篇:完成塊

基于UIView的block的動(dòng)畫允許你在動(dòng)畫結(jié)束的時(shí)候提供一個(gè)完成的動(dòng)作。CATranscation接口提供的+setCompletionBlock:方法也有同樣的功能。
在每次顏色變化結(jié)束之后切換到另一個(gè)旋轉(zhuǎn)90的動(dòng)畫

- (IBAction)changeColor {
    //begin a new transaction
    [CATransaction begin];
    //set the animation duration to 1 second
    [CATransaction setAnimationDuration:1.0];
    //add the spin animation on completion
    [CATransaction setCompletionBlock:^{
        //rotate the layer 90 degrees
        CGAffineTransform transform = self.colorLayer.affineTransform;
        transform = CGAffineTransformRotate(transform, M_PI_2);
        self.colorLayer.affineTransform = transform;
    }];
    //randomize the layer background color
    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;
    //commit the transaction
    [CATransaction commit];
}

注意旋轉(zhuǎn)動(dòng)畫要比顏色漸變快得多,這是因?yàn)橥瓿蓧K是在顏色漸變的事務(wù)提交并出棧之后才被執(zhí)行

第3篇:圖層行為

Core Animation通常對(duì)CALayer的所有屬性(可動(dòng)畫的屬性)做動(dòng)畫,但是UIView把它關(guān)聯(lián)的圖層的這個(gè)特性關(guān)閉了。為了更好說(shuō)明這一點(diǎn),我們需要知道隱式動(dòng)畫是如何實(shí)現(xiàn)的。
我們把改變屬性時(shí)CALayer自動(dòng)應(yīng)用的動(dòng)畫稱作行為,當(dāng)CALayer的屬性被修改時(shí)候,它會(huì)調(diào)用-actionForKey:方法,傳遞屬性的名稱。剩下的操作都在CALayer的頭文件中有詳細(xì)的說(shuō)明,實(shí)質(zhì)上是如下幾步:

  • 圖層首先檢測(cè)它是否有委托,并且是否實(shí)現(xiàn)CALayerDelegate協(xié)議指定的-actionForLayer:forKey方法。如果有,直接調(diào)用并返回結(jié)果。
  • 如果沒(méi)有委托,或者委托沒(méi)有實(shí)現(xiàn)-actionForLayer:forKey方法,圖層接著檢查包含屬性名稱對(duì)應(yīng)行為映射的actions字典。
  • 如果actions字典沒(méi)有包含對(duì)應(yīng)的屬性,那么圖層接著在它的style字典接著搜索屬性名。
  • 最后,如果在style里面也找不到對(duì)應(yīng)的行為,那么圖層將會(huì)直接調(diào)用定義了每個(gè)屬性的標(biāo)準(zhǔn)行為的-defaultActionForKey:方法。
    所以一輪完整的搜索結(jié)束之后,-actionForKey:要么返回空(這種情況下將不會(huì)有動(dòng)畫發(fā)生),要么是CAAction協(xié)議對(duì)應(yīng)的對(duì)象,最后CALayer拿這個(gè)結(jié)果去對(duì)先前和當(dāng)前的值做動(dòng)畫。

于是這就解釋了UIKit是如何禁用隱式動(dòng)畫的:每個(gè)UIView對(duì)它關(guān)聯(lián)的圖層都扮演了一個(gè)委托,并且提供了-actionForLayer:forKey的實(shí)現(xiàn)方法。當(dāng)不在一個(gè)動(dòng)畫塊的實(shí)現(xiàn)中,UIView對(duì)所有圖層行為返回nil,但是在動(dòng)畫block范圍之內(nèi),它就返回了一個(gè)非空值,我們可以用一個(gè)demo做個(gè)簡(jiǎn)單的實(shí)驗(yàn):

    //test layer action when outside of animation block
    NSLog(@"Outside: %@", [self.layerView actionForLayer:self.layerView.layer forKey:@"backgroundColor"]);
    //begin animation block
    [UIView beginAnimations:nil context:nil];
    //test layer action when inside of animation block
    NSLog(@"Inside: %@", [self.layerView actionForLayer:self.layerView.layer forKey:@"backgroundColor"]);
    //end animation block
    [UIView commitAnimations];

運(yùn)行程序,控制臺(tái)顯示結(jié)果如下:

$ LayerTest[21215:c07] Outside: <null>
$ LayerTest[21215:c07] Inside: <CABasicAnimation: 0x757f090>

于是我們可以預(yù)言,當(dāng)屬性在動(dòng)畫塊之外發(fā)生改變,UIView直接通過(guò)返回nil來(lái)禁用隱式動(dòng)畫。但如果在動(dòng)畫塊范圍之內(nèi),根據(jù)動(dòng)畫具體類型返回相應(yīng)的屬性,在這個(gè)例子就是CABasicAnimation(第八章“顯式動(dòng)畫”將會(huì)提到)。

當(dāng)然返回nil并不是禁用隱式動(dòng)畫唯一的辦法,CATransacition有個(gè)方法叫做+setDisableActions:,可以用來(lái)對(duì)所有屬性打開(kāi)或者關(guān)閉隱式動(dòng)畫。如果在清單7.2的[CATransaction begin]之后添加下面的代碼,同樣也會(huì)阻止動(dòng)畫的發(fā)生:

[CATransaction setDisableActions:YES];

總結(jié)一下,我們知道了如下幾點(diǎn)

UIView關(guān)聯(lián)的圖層禁用了隱式動(dòng)畫,對(duì)這種圖層做動(dòng)畫的唯一辦法就是使用UIView的動(dòng)畫函數(shù)(而不是依賴CATransaction),或者繼承UIView,并覆蓋-actionForLayer:forKey:方法,或者直接創(chuàng)建一個(gè)顯式動(dòng)畫(具體細(xì)節(jié)見(jiàn)第八章)。
對(duì)于單獨(dú)存在的圖層,我們可以通過(guò)實(shí)現(xiàn)圖層的-actionForLayer:forKey:委托方法,或者提供一個(gè)actions字典來(lái)控制隱式動(dòng)畫。

第4篇:呈現(xiàn)與模型

CALayer的屬性行為其實(shí)很不正常,因?yàn)楦淖円粋€(gè)圖層的屬性并沒(méi)有立刻生效,而是通過(guò)一段時(shí)間漸變更新。這是怎么做到的呢?

當(dāng)你改變一個(gè)圖層的屬性,屬性值的確是立刻更新的(如果你讀取它的數(shù)據(jù),你會(huì)發(fā)現(xiàn)它的值在你設(shè)置它的那一刻就已經(jīng)生效了),但是屏幕上并沒(méi)有馬上發(fā)生改變。這是因?yàn)槟阍O(shè)置的屬性并沒(méi)有直接調(diào)整圖層的外觀,相反,他只是定義了圖層動(dòng)畫結(jié)束之后將要變化的外觀。

在iOS中,屏幕每秒鐘重繪60次。如果動(dòng)畫時(shí)長(zhǎng)比60分之一秒要長(zhǎng),Core Animation就需要在設(shè)置一次新值和新值生效之間,對(duì)屏幕上的圖層進(jìn)行重新組織。這意味著CALayer除了“真實(shí)”值(就是你設(shè)置的值)之外,必須要知道當(dāng)前顯示在屏幕上的屬性值的記錄。
每個(gè)圖層屬性的顯示值都被存儲(chǔ)在一個(gè)叫做呈現(xiàn)圖層的獨(dú)立圖層當(dāng)中,他可以通過(guò)-presentationLayer方法來(lái)訪問(wèn)。這個(gè)呈現(xiàn)圖層實(shí)際上是模型圖層的復(fù)制,但是它的屬性值代表了在任何指定時(shí)刻當(dāng)前外觀效果。換句話說(shuō),你可以通過(guò)呈現(xiàn)圖層的值來(lái)獲取當(dāng)前屏幕上真正顯示出來(lái)的值

你可能注意到有一個(gè)叫做–modelLayer的方法。在呈現(xiàn)圖層上調(diào)用–modelLayer將會(huì)返回它正在呈現(xiàn)所依賴的CALayer。通常在一個(gè)圖層上調(diào)用-modelLayer會(huì)返回–self(實(shí)際上我們已經(jīng)創(chuàng)建的原始圖層就是一種數(shù)據(jù)模型)。

大多數(shù)情況下,你不需要直接訪問(wèn)呈現(xiàn)圖層,你可以通過(guò)和模型圖層的交互,來(lái)讓Core Animation更新顯示。兩種情況下呈現(xiàn)圖層會(huì)變得很有用,一個(gè)是同步動(dòng)畫,一個(gè)是處理用戶交互。

  • 如果你在實(shí)現(xiàn)一個(gè)基于定時(shí)器的動(dòng)畫(見(jiàn)第11章“基于定時(shí)器的動(dòng)畫”),而不僅僅是基于事務(wù)的動(dòng)畫,這個(gè)時(shí)候準(zhǔn)確地知道在某一時(shí)刻圖層顯示在什么位置就會(huì)對(duì)正確擺放圖層很有用了。
  • 如果你想讓你做動(dòng)畫的圖層響應(yīng)用戶輸入,你可以使用-hitTest:方法(見(jiàn)第三章“圖層幾何學(xué)”)來(lái)判斷指定圖層是否被觸摸,這時(shí)候?qū)Τ尸F(xiàn)圖層而不是模型圖層調(diào)用-hitTest:會(huì)顯得更有意義,因?yàn)槌尸F(xiàn)圖層代表了用戶當(dāng)前看到的圖層位置,而不是當(dāng)前動(dòng)畫結(jié)束之后的位置。

我們可以用一個(gè)簡(jiǎn)單的案例來(lái)證明后者(見(jiàn)下面代碼)。在這個(gè)例子中,點(diǎn)擊屏幕上的任意位置將會(huì)讓圖層平移到那里。點(diǎn)擊圖層本身可以隨機(jī)改變它的顏色。我們通過(guò)對(duì)呈現(xiàn)圖層調(diào)用-hitTest:來(lái)判斷是否被點(diǎn)擊。

如果修改代碼讓-hitTest:直接作用于colorLayer而不是呈現(xiàn)圖層,你會(huì)發(fā)現(xiàn)當(dāng)圖層移動(dòng)的時(shí)候它并不能正確顯示。這時(shí)候你就需要點(diǎn)擊圖層將要移動(dòng)到的位置而不是圖層本身來(lái)響應(yīng)點(diǎn)擊(這就是為什么用呈現(xiàn)圖層來(lái)響應(yīng)交互的原因)。

使用presentationLayer圖層來(lái)判斷當(dāng)前圖層位置

- (void)viewDidLoad {
    [super viewDidLoad];
    //create a red layer
    self.colorLayer = [CALayer layer];
    self.colorLayer.frame = CGRectMake(0, 0, 100, 100);
    self.colorLayer.position = CGPointMake(self.view.bounds.size.width / 2, self.view.bounds.size.height / 2);
    self.colorLayer.backgroundColor = [UIColor redColor].CGColor;
    [self.view.layer addSublayer:self.colorLayer];
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    //get the touch point
    CGPoint point = [[touches anyObject] locationInView:self.view];
    //check if we've tapped the moving layer
    if ([self.colorLayer.presentationLayer hitTest:point]) {
        //randomize the layer background color
        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;
    } else {
        //otherwise (slowly) move the layer to new position
        [CATransaction begin];
        [CATransaction setAnimationDuration:4.0];
        self.colorLayer.position = point;
        [CATransaction commit];
    }
}

總結(jié):

這一章討論了隱式動(dòng)畫,還有Core Animation對(duì)指定屬性選擇合適的動(dòng)畫行為的機(jī)制。同時(shí)你知道了UIKit是如何充分利用Core Animation的隱式動(dòng)畫機(jī)制來(lái)強(qiáng)化它的顯式系統(tǒng),以及動(dòng)畫是如何被默認(rèn)禁用并且當(dāng)需要的時(shí)候啟用的。最后,你了解了呈現(xiàn)和模型圖層,以及Core Animation是如何通過(guò)它們來(lái)判斷出圖層當(dāng)前位置以及將要到達(dá)的位置。

在下一章中,我們將研究Core Animation提供的顯式動(dòng)畫類型,既可以直接對(duì)圖層屬性做動(dòng)畫,也可以覆蓋默認(rèn)的圖層行為。

第八章:顯式動(dòng)畫

前言:

上一章介紹了隱式動(dòng)畫的概念。隱式動(dòng)畫是在iOS平臺(tái)創(chuàng)建動(dòng)態(tài)用戶界面的一種直接方式,也是UIKit動(dòng)畫機(jī)制的基礎(chǔ),不過(guò)它并不能涵蓋所有的動(dòng)畫類型。在這一章中,我們將要研究一下顯式動(dòng)畫,它能夠?qū)σ恍傩宰鲋付ǖ淖远x動(dòng)畫,或者創(chuàng)建非線性動(dòng)畫,比如沿著任意一條曲線移動(dòng)。

第1篇:屬性動(dòng)畫

當(dāng)使用-addAnimation:forKey:把動(dòng)畫添加到圖層,這里有一個(gè)到目前為止我們都設(shè)置為nil的key參數(shù)。這里的鍵是-animationForKey:方法找到對(duì)應(yīng)動(dòng)畫的唯一標(biāo)識(shí)符,而當(dāng)前動(dòng)畫的所有鍵都可以用animationKeys獲取。如果我們對(duì)每個(gè)動(dòng)畫都關(guān)聯(lián)一個(gè)唯一的鍵,就可以對(duì)每個(gè)圖層循環(huán)所有鍵,然后調(diào)用-animationForKey:來(lái)比對(duì)結(jié)果。盡管這不是一個(gè)優(yōu)雅的實(shí)現(xiàn)。
幸運(yùn)的是,還有一種更加簡(jiǎn)單的方法。像所有的NSObject子類一樣,CAAnimation實(shí)現(xiàn)了KVC(鍵-值-編碼)協(xié)議,于是你可以用-setValue:forKey:和-valueForKey:方法來(lái)存取屬性。但是CAAnimation有一個(gè)不同的性能:它更像一個(gè)NSDictionary,可以讓你隨意設(shè)置鍵值對(duì),即使和你使用的動(dòng)畫類所聲明的屬性并不匹配。
這意味著你可以對(duì)動(dòng)畫用任意類型打標(biāo)簽。在這里,我們給UIView類型的指針添加的動(dòng)畫,所以可以簡(jiǎn)單地判斷動(dòng)畫到底屬于哪個(gè)視圖,然后在委托方法中用這個(gè)信息正確地更新鐘的指針(見(jiàn)下面的代碼)。
使用KVC對(duì)動(dòng)畫打標(biāo)簽

        CABasicAnimation *animation = [CABasicAnimation animation];
        [self updateHandsAnimated:NO];
        animation.keyPath = @"transform";
        animation.toValue = [NSValue valueWithCATransform3D:transform];
        animation.duration = 0.5;
        animation.delegate = self;
        [animation setValue:handView forKey:@"handView"];
        [handView.layer addAnimation:animation forKey:nil];
  - (void)animationDidStop:(CABasicAnimation *)anim finished:(BOOL)flag {
    //set final position for hand view
    UIView *handView = [anim valueForKey:@"handView"];
    handView.layer.transform = [anim.toValue CATransform3DValue];
}

我們成功的識(shí)別出每個(gè)圖層停止動(dòng)畫的時(shí)間,然后更新它的變換到一個(gè)新值,很好。
不幸的是,即使做了這些,還是有個(gè)問(wèn)題,該代碼在模擬器上運(yùn)行的很好,但當(dāng)真正跑在iOS設(shè)備上時(shí),我們發(fā)現(xiàn)在-animationDidStop:finished:委托方法調(diào)用之前,指針會(huì)迅速返回到原始值,這個(gè)清單8.3圖層顏色發(fā)生的情況一樣。
問(wèn)題在于回調(diào)方法在動(dòng)畫完成之前已經(jīng)被調(diào)用了,但不能保證這發(fā)生在屬性動(dòng)畫返回初始狀態(tài)之前。這同時(shí)也很好地說(shuō)明了為什么要在真實(shí)的設(shè)備上測(cè)試動(dòng)畫代碼,而不僅僅是模擬器。
我們可以用一個(gè)fillMode屬性來(lái)解決這個(gè)問(wèn)題,下一章會(huì)詳細(xì)說(shuō)明,這里知道在動(dòng)畫之前設(shè)置它比在動(dòng)畫結(jié)束之后更新屬性更加方便。

關(guān)鍵幀動(dòng)畫

CABasicAnimation揭示了大多數(shù)隱式動(dòng)畫背后依賴的機(jī)制,這的確很有趣,但是顯式地給圖層添加CABasicAnimation相較于隱式動(dòng)畫而言,只能說(shuō)費(fèi)力不討好。
CAKeyframeAnimation是另一種UIKit沒(méi)有暴露出來(lái)但功能強(qiáng)大的類。和CABasicAnimation類似,CAKeyframeAnimation同樣是CAPropertyAnimation的一個(gè)子類,它依然作用于單一的一個(gè)屬性,但是和CABasicAnimation不一樣的是,它不限制于設(shè)置一個(gè)起始和結(jié)束的值,而是可以根據(jù)一連串隨意的值來(lái)做動(dòng)畫。
設(shè)置一個(gè)顏色的數(shù)組,然后通過(guò)關(guān)鍵幀動(dòng)畫播放出來(lái)(見(jiàn)下面的代碼)
使用CAKeyframeAnimation應(yīng)用一系列顏色的變化

 - (IBAction)changeColor {
    //create a keyframe animation
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
    animation.keyPath = @"backgroundColor";
    animation.duration = 2.0;
    animation.values = @[
                         (__bridge id)[UIColor blueColor].CGColor,
                         (__bridge id)[UIColor redColor].CGColor,
                         (__bridge id)[UIColor greenColor].CGColor,
                         (__bridge id)[UIColor blueColor].CGColor ];
    //apply animation to layer
    [self.colorLayer addAnimation:animation forKey:nil];
}

運(yùn)行它,你會(huì)發(fā)現(xiàn)動(dòng)畫通過(guò)顏色不斷循環(huán),但效果看起來(lái)有些奇怪。原因在于動(dòng)畫以一個(gè)恒定的步調(diào)在運(yùn)行。當(dāng)在每個(gè)動(dòng)畫之間過(guò)渡的時(shí)候并沒(méi)有減速,這就產(chǎn)生了一個(gè)略微奇怪的效果,為了讓動(dòng)畫看起來(lái)更自然,我們需要調(diào)整一下緩沖,第十章將會(huì)詳細(xì)說(shuō)明。
提供一個(gè)數(shù)組的值就可以按照顏色變化做動(dòng)畫,但一般來(lái)說(shuō)用數(shù)組來(lái)描述動(dòng)畫運(yùn)動(dòng)并不直觀。CAKeyframeAnimation有另一種方式去指定動(dòng)畫,就是使用CGPath。path屬性可以用一種直觀的方式,使用Core Graphics函數(shù)定義運(yùn)動(dòng)序列來(lái)繪制動(dòng)畫。

虛擬屬性

之前提到過(guò)屬性動(dòng)畫實(shí)際上是針對(duì)于關(guān)鍵路徑而不是一個(gè)鍵,這就意味著可以對(duì)子屬性甚至是虛擬屬性做動(dòng)畫。但是虛擬屬性到底是什么呢?

考慮一個(gè)旋轉(zhuǎn)的動(dòng)畫:如果想要對(duì)一個(gè)物體做旋轉(zhuǎn)的動(dòng)畫,那就需要作用于transform屬性,因?yàn)镃ALayer沒(méi)有顯式提供角度或者方向之類的屬性。
解決方案:為了旋轉(zhuǎn)圖層,我們可以對(duì)transform.rotation關(guān)鍵路徑應(yīng)用動(dòng)畫,而不是transform本身。
對(duì)虛擬的transform.rotation屬性做動(dòng)畫

     //add the ship
    CALayer *shipLayer = [CALayer layer];
    shipLayer.frame = CGRectMake(0, 0, 128, 128);
    shipLayer.position = CGPointMake(150, 150);
    shipLayer.contents = (__bridge id)[UIImage imageNamed: @"Ship.png"].CGImage;
    [self.containerView.layer addSublayer:shipLayer];
    //animate the ship rotation
    CABasicAnimation *animation = [CABasicAnimation animation];
    animation.keyPath = @"transform.rotation";
    animation.duration = 2.0;
    animation.byValue = @(M_PI * 2);
    [shipLayer addAnimation:animation forKey:nil];”

用transform.rotation而不是transform做動(dòng)畫的好處如下:

  • 我們可以不通過(guò)關(guān)鍵幀一步旋轉(zhuǎn)多于180度的動(dòng)畫。
  • 可以用相對(duì)值而不是絕對(duì)值旋轉(zhuǎn)(設(shè)置byValue而不是toValue)。
  • 可以不用創(chuàng)建CATransform3D,而是使用一個(gè)簡(jiǎn)單的數(shù)值來(lái)指定角度。
  • 不會(huì)和transform.position或者transform.scale沖突(同樣是使用關(guān)鍵路徑來(lái)做獨(dú)立的動(dòng)畫屬性)。

transform.rotation屬性有一個(gè)奇怪的問(wèn)題是它其實(shí)并不存在。這是因?yàn)镃ATransform3D并不是一個(gè)對(duì)象,它實(shí)際上是一個(gè)結(jié)構(gòu)體,也沒(méi)有符合KVC相關(guān)屬性,transform.rotation實(shí)際上是一個(gè)CALayer用于處理動(dòng)畫變換的虛擬屬性。

第2篇:動(dòng)畫組

CABasicAnimation和CAKeyframeAnimation僅僅作用于單獨(dú)的屬性,而CAAnimationGroup可以把這些動(dòng)畫組合在一起。CAAnimationGroup是另一個(gè)繼承于CAAnimation的子類,它添加了一個(gè)animations數(shù)組的屬性,用來(lái)組合別的動(dòng)畫。

第3篇:過(guò)渡

屬性動(dòng)畫只對(duì)圖層的可動(dòng)畫屬性起作用,所以如果要改變一個(gè)不能動(dòng)畫的屬性(比如圖片),或者從層級(jí)關(guān)系中添加或者移除圖層,屬性動(dòng)畫將不起作用。

于是就有了過(guò)渡的概念。過(guò)渡并不像屬性動(dòng)畫那樣平滑地在兩個(gè)值之間做動(dòng)畫,而是影響到整個(gè)圖層的變化。過(guò)渡動(dòng)畫首先展示之前的圖層外觀,然后通過(guò)一個(gè)交換過(guò)渡到新的外觀。

為了創(chuàng)建一個(gè)過(guò)渡動(dòng)畫,我們將使用CATransition,同樣是另一個(gè)CAAnimation的子類,和別的子類不同,CATransition有一個(gè)type和subtype來(lái)標(biāo)識(shí)變換效果。type屬性是一個(gè)NSString類型,可以被設(shè)置成如下類型:

  • kCATransitionFade
  • kCATransitionMoveIn
  • kCATransitionPush
  • kCATransitionReveal

到目前為止你只能使用上述四種類型,但你可以通過(guò)一些別的方法來(lái)自定義過(guò)渡效果,后續(xù)會(huì)詳細(xì)介紹。
默認(rèn)的過(guò)渡類型是kCATransitionFade,當(dāng)你在改變圖層屬性之后,就創(chuàng)建了一個(gè)平滑的淡入淡出效果。

我們?cè)诘谄哒碌睦又芯鸵呀?jīng)用到過(guò)kCATransitionPush,它創(chuàng)建了一個(gè)新的圖層,從邊緣的一側(cè)滑動(dòng)進(jìn)來(lái),把舊圖層從另一側(cè)推出去的效果。

kCATransitionMoveIn和kCATransitionReveal與kCATransitionPush類似,都實(shí)現(xiàn)了一個(gè)定向滑動(dòng)的動(dòng)畫,但是有一些細(xì)微的不同,kCATransitionMoveIn從頂部滑動(dòng)進(jìn)入,但不像推送動(dòng)畫那樣把老土層推走,然而kCATransitionReveal把原始的圖層滑動(dòng)出去來(lái)顯示新的外觀,而不是把新的圖層滑動(dòng)進(jìn)入。

后面三種過(guò)渡類型都有一個(gè)默認(rèn)的動(dòng)畫方向,它們都從左側(cè)滑入,但是你可以通過(guò)subtype來(lái)控制它們的方向,提供了如下四種類型:

  • kCATransitionFromRight
  • kCATransitionFromLeft
  • kCATransitionFromTop
  • kCATransitionFromBottom

一個(gè)簡(jiǎn)單的用CATransition來(lái)對(duì)非動(dòng)畫屬性做動(dòng)畫的例子如下面所示,這里我們對(duì)UIImage的image屬性做修改,但是隱式動(dòng)畫或者CAPropertyAnimation都不能對(duì)它做動(dòng)畫,因?yàn)镃ore Animation不知道如何在插圖圖片。通過(guò)對(duì)圖層應(yīng)用一個(gè)淡入淡出的過(guò)渡,我們可以忽略它的內(nèi)容來(lái)做平滑動(dòng)畫,我們來(lái)嘗試修改過(guò)渡的type常量來(lái)觀察其它效果。
使用CATransition對(duì)圖像平滑淡入淡出

interface ViewController ()
@property (nonatomic, weak) IBOutlet UIImageView *imageView;
@property (nonatomic, copy) NSArray *images;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //set up images
    self.images = @[[UIImage imageNamed:@"Anchor.png"],
                    [UIImage imageNamed:@"Cone.png"],
                    [UIImage imageNamed:@"Igloo.png"],
                    [UIImage imageNamed:@"Spaceship.png"]];
}

- (IBAction)switchImage {
    //set up crossfade transition
    CATransition *transition = [CATransition animation];
    transition.type = kCATransitionFade;
    //apply transition to imageview backing layer
    [self.imageView.layer addAnimation:transition forKey:nil];
    //cycle to next image
    UIImage *currentImage = self.imageView.image;
    NSUInteger index = [self.images indexOfObject:currentImage];
    index = (index + 1) % [self.images count];
    self.imageView.image = self.images[index];
}
@end

你可以從代碼中看出,過(guò)渡動(dòng)畫和之前的屬性動(dòng)畫或者動(dòng)畫組添加到圖層上的方式一致,都是通過(guò)-addAnimation:forKey:方法。但是和屬性動(dòng)畫不同的是,對(duì)指定的圖層一次只能使用一次CATransition,因此,無(wú)論你對(duì)動(dòng)畫的鍵設(shè)置什么值,過(guò)渡動(dòng)畫都會(huì)對(duì)它的鍵設(shè)置成“transition”,也就是常量kCATransition。

隱式過(guò)渡

CATransision可以對(duì)圖層任何變化平滑過(guò)渡的事實(shí)使得它成為那些不好做動(dòng)畫的屬性圖層行為的理想候選。蘋果當(dāng)然意識(shí)到了這點(diǎn),并且當(dāng)設(shè)置了CALayer的content屬性的時(shí)候,CATransition的確是默認(rèn)的行為。但是對(duì)于視圖關(guān)聯(lián)的圖層,或者是其他隱式動(dòng)畫的行為,這個(gè)特性依然是被禁用的,但是對(duì)于你自己創(chuàng)建的圖層,這意味著對(duì)圖層contents圖片做的改動(dòng)都會(huì)自動(dòng)附上淡入淡出的動(dòng)畫。

對(duì)圖層樹(shù)的動(dòng)畫

CATransition并不作用于指定的圖層屬性,這就是說(shuō)你可以在即使不能準(zhǔn)確得知改變了什么的情況下對(duì)圖層做動(dòng)畫,例如,在不知道UITableView哪一行被添加或者刪除的情況下,直接就可以平滑地刷新它,或者在不知道UIViewController內(nèi)部的視圖層級(jí)的情況下對(duì)兩個(gè)不同的實(shí)例做過(guò)渡動(dòng)畫。

這些例子和我們之前所討論的情況完全不同,因?yàn)樗鼈儾粌H涉及到圖層的屬性,而且是整個(gè)圖層樹(shù)的改變--我們?cè)谶@種動(dòng)畫的過(guò)程中手動(dòng)在層級(jí)關(guān)系中添加或者移除圖層。

這里用到了一個(gè)小詭計(jì),要確保CATransition添加到的圖層在過(guò)渡動(dòng)畫發(fā)生時(shí)不會(huì)在樹(shù)狀結(jié)構(gòu)中被移除,否則CATransition將會(huì)和圖層一起被移除。一般來(lái)說(shuō),你只需要將動(dòng)畫添加到被影響圖層的superlayer。

在下列代碼中,我們展示了如何在UITabBarController切換標(biāo)簽的時(shí)候添加淡入淡出的動(dòng)畫。這里我們建立了默認(rèn)的標(biāo)簽應(yīng)用程序模板,然后用UITabBarControllerDelegate的-tabBarController:didSelectViewController:方法來(lái)應(yīng)用過(guò)渡動(dòng)畫。我們把動(dòng)畫添加到UITabBarController的視圖圖層上,于是在標(biāo)簽被替換的時(shí)候動(dòng)畫不會(huì)被移除。

對(duì)UITabBarController做動(dòng)畫

 - (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
//set up crossfade transition
    CATransition *transition = [CATransition animation];
    transition.type = kCATransitionFade;
    //apply transition to tab bar controller's view
    [self.tabBarController.view.layer addAnimation:transition forKey:nil];
}

自定義動(dòng)畫

我們證實(shí)了過(guò)渡是一種對(duì)那些不太好做平滑動(dòng)畫屬性的強(qiáng)大工具,但是CATransition的提供的動(dòng)畫類型太少了。

更奇怪的是蘋果通過(guò)UIView +transitionFromView:toView:duration:options:completion:和+transitionWithView:duration:options:animations:方法提供了Core Animation的過(guò)渡特性。但是這里的可用的過(guò)渡選項(xiàng)和CATransition的type屬性提供的常量完全不同。
但這并不意味著在iOS上就不能實(shí)現(xiàn)自定義的過(guò)渡效果了。這只是意味著你需要做一些額外的工作。就像之前提到的那樣,過(guò)渡動(dòng)畫做基礎(chǔ)的原則就是對(duì)原始的圖層外觀截圖,然后添加一段動(dòng)畫,平滑過(guò)渡到圖層改變之后那個(gè)截圖的效果。如果我們知道如何對(duì)圖層截圖,我們就可以使用屬性動(dòng)畫來(lái)代替CATransition或者是UIKit的過(guò)渡方法來(lái)實(shí)現(xiàn)動(dòng)畫。
事實(shí)證明,對(duì)圖層做截圖還是很簡(jiǎn)單的。CALayer有一個(gè)-renderInContext:方法,可以通過(guò)把它繪制到Core Graphics的上下文中捕獲當(dāng)前內(nèi)容的圖片,然后在另外的視圖中顯示出來(lái)。如果我們把這個(gè)截屏視圖置于原始視圖之上,就可以遮住真實(shí)視圖的所有變化,于是重新創(chuàng)建了一個(gè)簡(jiǎn)單的過(guò)渡效果。

清單8.14演示了一個(gè)基本的實(shí)現(xiàn)。我們對(duì)當(dāng)前視圖狀態(tài)截圖,然后在我們改變?cè)家晥D的背景色的時(shí)候?qū)貓D快速轉(zhuǎn)動(dòng)并且淡出,圖8.5展示了我們自定義的過(guò)渡效果。

為了讓事情更簡(jiǎn)單,我們用UIView -animateWithDuration:completion:方法來(lái)實(shí)現(xiàn)。雖然用CABasicAnimation可以達(dá)到同樣的效果,但是那樣的話我們就需要對(duì)圖層的變換和不透明屬性創(chuàng)建單獨(dú)的動(dòng)畫,然后當(dāng)動(dòng)畫結(jié)束的是哦戶在CAAnimationDelegate中把coverView從屏幕中移除。

用renderInContext:創(chuàng)建自定義過(guò)渡效果

@implementation ViewController
- (IBAction)performTransition {
    //preserve the current view snapshot
    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;
    [self.view addSubview:coverView];
    //update the view (we'll simply randomize the layer background color)
    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];
    //perform animation (anything you like)
    [UIView animateWithDuration:1.0 animations:^{
        //scale, rotate and fade the view
        CGAffineTransform transform = CGAffineTransformMakeScale(0.01, 0.01);
        transform = CGAffineTransformRotate(transform, M_PI_2);
        coverView.transform = transform;
        coverView.alpha = 0.0;
    } completion:^(BOOL finished) {
        //remove the cover view now we're finished with it
        [coverView removeFromSuperview];
    }];
}
@end

這里有個(gè)警告:-renderInContext:捕獲了圖層的圖片和子圖層,但是不能對(duì)子圖層正確地處理變換效果,而且對(duì)視頻和OpenGL內(nèi)容也不起作用。但是用CATransition,或者用私有的截屏方式就沒(méi)有這個(gè)限制了。

第4篇:在動(dòng)畫過(guò)程中取消動(dòng)畫

之前提到過(guò),你可以用-addAnimation:forKey:方法中的key參數(shù)來(lái)在添加動(dòng)畫之后檢索一個(gè)動(dòng)畫,使用如下方法:

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

但并不支持在動(dòng)畫運(yùn)行過(guò)程中修改動(dòng)畫,所以這個(gè)方法主要用來(lái)檢測(cè)動(dòng)畫的屬性,或者判斷它是否被添加到當(dāng)前圖層中。
為了終止一個(gè)指定的動(dòng)畫,你可以用如下方法把它從圖層移除掉:

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

或者移除所有動(dòng)畫:

- (void)removeAllAnimations;

動(dòng)畫一旦被移除,圖層的外觀就立刻更新到當(dāng)前的模型圖層的值。一般說(shuō)來(lái),動(dòng)畫在結(jié)束之后被自動(dòng)移除,除非設(shè)置removedOnCompletion為NO,如果你設(shè)置動(dòng)畫在結(jié)束之后不被自動(dòng)移除,那么當(dāng)它不需要的時(shí)候你要手動(dòng)移除它;否則它會(huì)一直存在于內(nèi)存中,直到圖層被銷毀。

我們來(lái)擴(kuò)展之前旋轉(zhuǎn)飛船的示例,這里添加一個(gè)按鈕來(lái)停止或者啟動(dòng)動(dòng)畫。這一次我們用一個(gè)非nil的值作為動(dòng)畫的鍵,以便之后可以移除它。-animationDidStop:finished:方法中的flag參數(shù)表明了動(dòng)畫是自然結(jié)束還是被打斷,我們可以在控制臺(tái)打印出來(lái)。如果你用停止按鈕來(lái)終止動(dòng)畫,它會(huì)打印NO,如果允許它完成,它會(huì)打印YES。

總結(jié):

這一章中,我們涉及了屬性動(dòng)畫(你可以對(duì)單獨(dú)的圖層屬性動(dòng)畫有更加具體的控制),動(dòng)畫組(把多個(gè)屬性動(dòng)畫組合成一個(gè)獨(dú)立單元)以及過(guò)度(影響整個(gè)圖層,可以用來(lái)對(duì)圖層的任何內(nèi)容做任何類型的動(dòng)畫,包括子圖層的添加和移除)。
在第九章中,我們繼續(xù)學(xué)習(xí)CAMediaTiming協(xié)議,來(lái)看一看Core Animation是怎樣處理逝去的時(shí)間。

第九章:圖層時(shí)間

前言:

在上面兩章中,我們探討了可以用CAAnimation和它的子類實(shí)現(xiàn)的多種圖層動(dòng)畫。動(dòng)畫的發(fā)生是需要持續(xù)一段時(shí)間的,所以計(jì)時(shí)對(duì)整個(gè)概念來(lái)說(shuō)至關(guān)重要。在這一章中,我們來(lái)看看CAMediaTiming,看看Core Animation是如何跟蹤時(shí)間的。

第1篇: CAMediaTiming協(xié)議

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

持續(xù)和重復(fù):

duration(CAMediaTiming的屬性之一),duration是一個(gè)CFTimeInterval的類型(類似于NSTimeInterval的一種雙精度浮點(diǎn)類型),對(duì)將要進(jìn)行的動(dòng)畫的一次迭代指定了時(shí)間。
這里的一次迭代是什么意思呢?CAMediaTiming另外還有一個(gè)屬性叫做repeatCount,代表動(dòng)畫重復(fù)的迭代次數(shù)。
創(chuàng)建重復(fù)動(dòng)畫的另一種方式是使用repeatDuration屬性,它讓動(dòng)畫重復(fù)一個(gè)指定的時(shí)間,而不是指定次數(shù)。你甚至設(shè)置一個(gè)叫做autoreverses的屬性(BOOL類型)在每次間隔交替循環(huán)過(guò)程中自動(dòng)回放。這對(duì)于播放一段連續(xù)非循環(huán)的動(dòng)畫很有用,例如打開(kāi)一扇門,然后關(guān)上它
把repeatDuration設(shè)置為INFINITY,于是動(dòng)畫無(wú)限循環(huán)播放,設(shè)置repeatCount為INFINITY也有同樣的效果。注意repeatCount和repeatDuration可能會(huì)相互沖突,所以你只要對(duì)其中一個(gè)指定非零值。對(duì)兩個(gè)屬性都設(shè)置非0值的行為沒(méi)有被定義。

相對(duì)時(shí)間:

每次討論到Core Animation,時(shí)間都是相對(duì)的,每個(gè)動(dòng)畫都有它自己描述的時(shí)間,可以獨(dú)立地加速,延時(shí)或者偏移。

beginTime指定了動(dòng)畫開(kāi)始之前的的延遲時(shí)間。這里的延遲從動(dòng)畫添加到可見(jiàn)圖層的那一刻開(kāi)始測(cè)量,默認(rèn)是0(就是說(shuō)動(dòng)畫會(huì)立刻執(zhí)行)。

speed是一個(gè)時(shí)間的倍數(shù),默認(rèn)1.0,減少它會(huì)減慢圖層/動(dòng)畫的時(shí)間,增加它會(huì)加快速度。如果2.0的速度,那么對(duì)于一個(gè)duration為1的動(dòng)畫,實(shí)際上在0.5秒的時(shí)候就已經(jīng)完成了。

timeOffset和beginTime類似,但是和增加beginTime導(dǎo)致的延遲動(dòng)畫不同,增加timeOffset只是讓動(dòng)畫快進(jìn)到某一點(diǎn),例如,對(duì)于一個(gè)持續(xù)1秒的動(dòng)畫來(lái)說(shuō),設(shè)置timeOffset為0.5意味著動(dòng)畫將從一半的地方開(kāi)始。
和beginTime不同的是,timeOffset并不受speed的影響。所以如果你把speed設(shè)為2.0,把timeOffset設(shè)置為0.5,那么你的動(dòng)畫將從動(dòng)畫最后結(jié)束的地方開(kāi)始,因?yàn)?秒的動(dòng)畫實(shí)際上被縮短到了0.5秒。然而即使使用了timeOffset讓動(dòng)畫從結(jié)束的地方開(kāi)始,它仍然播放了一個(gè)完整的時(shí)長(zhǎng),這個(gè)動(dòng)畫僅僅是循環(huán)了一圈,然后從頭開(kāi)始播放。

fillMode:

對(duì)于beginTime非0的一段動(dòng)畫來(lái)說(shuō),會(huì)出現(xiàn)一個(gè)當(dāng)動(dòng)畫添加到圖層上但什么也沒(méi)發(fā)生的狀態(tài)。類似的,removeOnCompletion被設(shè)置為NO的動(dòng)畫將會(huì)在動(dòng)畫結(jié)束的時(shí)候仍然保持之前的狀態(tài)。這就產(chǎn)生了一個(gè)問(wèn)題,當(dāng)動(dòng)畫開(kāi)始之前和動(dòng)畫結(jié)束之后,被設(shè)置動(dòng)畫的屬性將會(huì)是什么值呢?

一種可能是屬性和動(dòng)畫沒(méi)被添加之前保持一致,也就是在模型圖層定義的值(見(jiàn)第七章“隱式動(dòng)畫”,模型圖層和呈現(xiàn)圖層的解釋)。

另一種可能是保持動(dòng)畫開(kāi)始之前那一幀,或者動(dòng)畫結(jié)束之后的那一幀。這就是所謂的填充,因?yàn)閯?dòng)畫開(kāi)始和結(jié)束的值用來(lái)填充開(kāi)始之前和結(jié)束之后的時(shí)間。

這種行為就交給開(kāi)發(fā)者了,它可以被CAMediaTiming的fillMode來(lái)控制。fillMode是一個(gè)NSString類型,可以接受如下四種常量:

  • kCAFillModeForwards
  • kCAFillModeBackwards
  • kCAFillModeBoth
  • kCAFillModeRemoved
    默認(rèn)是kCAFillModeRemoved,當(dāng)動(dòng)畫不再播放的時(shí)候就顯示圖層模型指定的值剩下的三種類型向前,向后或者即向前又向后去填充動(dòng)畫狀態(tài),使得動(dòng)畫在開(kāi)始前或者結(jié)束后仍然保持開(kāi)始和結(jié)束那一刻的值。

這就對(duì)避免在動(dòng)畫結(jié)束的時(shí)候急速返回提供另一種方案(見(jiàn)第八章)。但是記住了,當(dāng)用它來(lái)解決這個(gè)問(wèn)題的時(shí)候,需要把removeOnCompletion設(shè)置為NO,另外需要給動(dòng)畫添加一個(gè)非空的鍵,于是可以在不需要?jiǎng)赢嫷臅r(shí)候把它從圖層上移除。

第2篇: 層級(jí)關(guān)系時(shí)間(書(shū)中無(wú))

在第三章“圖層幾何學(xué)”中,你已經(jīng)了解到每個(gè)圖層是如何相對(duì)在圖層樹(shù)中的父圖層定義 它的坐標(biāo)系的。動(dòng)畫時(shí)間和它類似,每個(gè)動(dòng)畫和圖層在時(shí)間上都有它自己的層級(jí)概念,相對(duì)于它的父親來(lái)測(cè)量。對(duì)圖層調(diào)整時(shí)間將會(huì)影響到它本身和子圖層的動(dòng)畫, 但不會(huì)影響到父圖層。另一個(gè)相似點(diǎn)是所有的動(dòng)畫都被按照層級(jí)組合(使用CAAnimationGroup實(shí)例)。

對(duì)CALayer或者 CAGroupAnimation調(diào)整duration和repeatCount/repeatDuration屬性并不會(huì)影響到子動(dòng)畫。但是 beginTime,timeOffset和speed屬性將會(huì)影響到子動(dòng)畫。然而在層級(jí)關(guān)系中,beginTime指定了父圖層開(kāi)始動(dòng)畫(或者組合關(guān)系 中的父動(dòng)畫)和對(duì)象將要開(kāi)始自己動(dòng)畫之間的偏移。類似的,調(diào)整CALayer和CAGroupAnimation的speed屬性將會(huì)對(duì)動(dòng)畫以及子動(dòng)畫速 度應(yīng)用一個(gè)縮放的因子。

全局時(shí)間和本地時(shí)間

CoreAnimation 有一個(gè)全局時(shí)間的概念,也就是所謂的馬赫時(shí)間(“馬赫”實(shí)際上是iOS和Mac OS系統(tǒng)內(nèi)核的命名)。馬赫時(shí)間在設(shè)備上所有進(jìn)程都是全局的--但是在不同設(shè)備上并不是全局的--不過(guò)這已經(jīng)足夠?qū)?dòng)畫的參考點(diǎn)提供便利了,你可以使用 CACurrentMediaTime函數(shù)來(lái)訪問(wèn)馬赫時(shí)間:

CFTimeInterval time = CACurrentMediaTime();

這個(gè)函數(shù)返回的值其實(shí)無(wú)關(guān)緊要(它返回了設(shè)備自從上次啟動(dòng)后的秒數(shù),并不是你所關(guān)心的),它真實(shí)的作用在于對(duì)動(dòng)畫的時(shí)間測(cè)量提供了一個(gè)相對(duì)值。注意當(dāng)設(shè)備休眠的時(shí)候馬赫時(shí)間會(huì)暫停,也就是所有的CAAnimations(基于馬赫時(shí)間)同樣也會(huì)暫停。

因此馬赫時(shí)間對(duì)長(zhǎng)時(shí)間測(cè)量并不有用。比如用CACurrentMediaTime去更新一個(gè)實(shí)時(shí)鬧鐘并不明智。(可以用[NSDate date]代替,就像第三章例子所示)。

每 個(gè)CALayer和CAAnimation實(shí)例都有自己本地時(shí)間的概念,是根據(jù)父圖層/動(dòng)畫層級(jí)關(guān)系中的beginTime,timeOffset和 speed屬性計(jì)算。就和轉(zhuǎn)換不同圖層之間坐標(biāo)關(guān)系一樣,CALayer同樣也提供了方法來(lái)轉(zhuǎn)換不同圖層之間的本地時(shí)間。如下:

- (CFTimeInterval)convertTime:(CFTimeInterval)t fromLayer:(CALayer *)l; 
- (CFTimeInterval)convertTime:(CFTimeInterval)t toLayer:(CALayer *)l;

當(dāng)用來(lái)同步不同圖層之間有不同的speed,timeOffset和beginTime的動(dòng)畫,這些方法會(huì)很有用。

暫停,倒回和快進(jìn)

設(shè) 置動(dòng)畫的speed屬性為0可以暫停動(dòng)畫,但在動(dòng)畫被添加到圖層之后不太可能再修改它了,所以不能對(duì)正在進(jìn)行的動(dòng)畫使用這個(gè)屬性。給圖層添加一個(gè) CAAnimation實(shí)際上是給動(dòng)畫對(duì)象做了一個(gè)不可改變的拷貝,所以對(duì)原始動(dòng)畫對(duì)象屬性的改變對(duì)真實(shí)的動(dòng)畫并沒(méi)有作用。相反,直接用 -animationForKey:來(lái)檢索圖層正在進(jìn)行的動(dòng)畫可以返回正確的動(dòng)畫對(duì)象,但是修改它的屬性將會(huì)拋出異常。

如果移除圖層正在進(jìn)行的動(dòng)畫,圖層將會(huì)急速返回動(dòng)畫之前的狀態(tài)。但如果在動(dòng)畫移除之前拷貝呈現(xiàn)圖層到模型圖層,動(dòng)畫將會(huì)看起來(lái)暫停在那里。但是不好的地方在于之后就不能再恢復(fù)動(dòng)畫了。

一個(gè)簡(jiǎn)單的方法是可以利用CAMediaTiming來(lái)暫停圖層本身。如果把圖層的speed設(shè)置成0,它會(huì)暫停任何添加到圖層上的動(dòng)畫。類似的,設(shè)置speed大于1.0將會(huì)快進(jìn),設(shè)置成一個(gè)負(fù)值將會(huì)倒回動(dòng)畫。

通過(guò)增加主窗口圖層的speed,可以暫停整個(gè)應(yīng)用程序的動(dòng)畫。這對(duì)UI自動(dòng)化提供了好處,我們可以加速所有的視圖動(dòng)畫來(lái)進(jìn)行自動(dòng)化測(cè)試(注意對(duì)于在主窗口之外的視圖并不會(huì)被影響,比如UIAlertview)。可以在app delegate設(shè)置如下進(jìn)行驗(yàn)證:

self.window.layer.speed = 100;

你也可以通過(guò)這種方式來(lái)減速,但其實(shí)也可以在模擬器通過(guò)切換慢速動(dòng)畫來(lái)實(shí)現(xiàn)。

第3篇:手動(dòng)動(dòng)畫

timeOffset一個(gè)很有用的功能在于你可以它可以讓你手動(dòng)控制動(dòng)畫進(jìn)程,通過(guò)設(shè)置speed為0,可以禁用動(dòng)畫的自動(dòng)播放,然后來(lái)使用timeOffset來(lái)來(lái)回顯示動(dòng)畫序列。這可以使得運(yùn)用手勢(shì)來(lái)手動(dòng)控制動(dòng)畫變得很簡(jiǎn)單。

舉個(gè)簡(jiǎn)單的例子:還是之前關(guān)門的動(dòng)畫,修改代碼來(lái)用手勢(shì)控制動(dòng)畫。我們給視圖添加一個(gè)UIPanGestureRecognizer,然后用timeOffset左右搖晃。

因?yàn)樵趧?dòng)畫添加到圖層之后不能再做修改了,我們來(lái)通過(guò)調(diào)整layer的timeOffset達(dá)到同樣的效果。

通過(guò)觸摸手勢(shì)手動(dòng)控制動(dòng)畫

@interface ViewController ()
@property (nonatomic, weak) UIView *containerView;
@property (nonatomic, strong) CALayer *doorLayer;
@end

@implementation ViewController

 - (void)viewDidLoad {
    [super viewDidLoad];
    //add the door
    self.doorLayer = [CALayer layer];
    self.doorLayer.frame = CGRectMake(0, 0, 128, 256);
    self.doorLayer.position = CGPointMake(150 - 64, 150);
    self.doorLayer.anchorPoint = CGPointMake(0, 0.5);
    self.doorLayer.contents = (__bridge id)[UIImage imageNamed:@"Door.png"].CGImage;
    [self.containerView.layer addSublayer:self.doorLayer];
    //apply perspective transform
    CATransform3D perspective = CATransform3DIdentity;
    perspective.m34 = -1.0 / 500.0;
    self.containerView.layer.sublayerTransform = perspective;
    //add pan gesture recognizer to handle swipes
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] init];
    [pan addTarget:self action:@selector(pan:)];
    [self.view addGestureRecognizer:pan];
    //pause all layer animations
    self.doorLayer.speed = 0.0;
    //apply swinging animation (which won't play because layer is paused)
    CABasicAnimation *animation = [CABasicAnimation animation];
    animation.keyPath = @"transform.rotation.y";
    animation.toValue = @(-M_PI_2);
    animation.duration = 1.0;
    [self.doorLayer addAnimation:animation forKey:nil];
}

- (void)pan:(UIPanGestureRecognizer *)pan {
    //get horizontal component of pan gesture
    CGFloat x = [pan translationInView:self.view].x;
//convert from points to animation duration //using a reasonable scale factor
    x /= 200.0f;
    //update timeOffset and clamp result
    CFTimeInterval timeOffset = self.doorLayer.timeOffset;
    timeOffset = MIN(0.999, MAX(0.0, timeOffset - x));
    self.doorLayer.timeOffset = timeOffset;
    //reset pan gesture
    [pan setTranslation:CGPointZero inView:self.view];
}
@end

這其實(shí)是個(gè)小詭計(jì),也許相對(duì)于設(shè)置個(gè)動(dòng)畫然后每次顯示一幀而言,用移動(dòng)手勢(shì)來(lái)直接設(shè)置門的transform會(huì)更簡(jiǎn)單。
在這個(gè)例子中的確是這樣,但是對(duì)于比如說(shuō)關(guān)鍵這這樣更加復(fù)雜的情況,或者有多個(gè)圖層的動(dòng)畫組,相對(duì)于實(shí)時(shí)計(jì)算每個(gè)圖層的屬性而言,這就顯得方便的多了。

總結(jié):

在這一章,我們了解了CAMediaTiming協(xié)議,以及Core Animation用來(lái)操作時(shí)間控制動(dòng)畫的機(jī)制。在下一章,我們將要接觸緩沖,另一個(gè)用來(lái)使動(dòng)畫更加真實(shí)的操作時(shí)間的技術(shù)。

第十章:緩沖

前言:

在第九章“圖層時(shí)間”中,我們討論了動(dòng)畫時(shí)間和CAMediaTiming協(xié)議。現(xiàn)在我們來(lái)看一下另一個(gè)和時(shí)間相關(guān)的機(jī)制--所謂的緩沖。Core Animation使用緩沖來(lái)使動(dòng)畫移動(dòng)更平滑更自然,而不是看起來(lái)的那種機(jī)械和人工,在這一章我們將要研究如何對(duì)你的動(dòng)畫控制和自定義緩沖曲線。

第1篇:動(dòng)畫速度

動(dòng)畫實(shí)際上就是一段時(shí)間內(nèi)的變化,這就暗示了變化一定是隨著某個(gè)特定的速率進(jìn)行。速率由以下公式計(jì)算而來(lái):

velocity = change / time

上面的等式假設(shè)了速度在整個(gè)動(dòng)畫過(guò)程中都是恒定不變的(就如同第八章“顯式動(dòng)畫”的情況),對(duì)于這種恒定速度的動(dòng)畫我們稱之為“線性步調(diào)”,而且從技術(shù)的角度而言這也是實(shí)現(xiàn)動(dòng)畫最簡(jiǎn)單的方式,但也是完全不真實(shí)的一種效果。
我們?nèi)绾卧趧?dòng)畫中實(shí)現(xiàn)這種加速度呢?一種方法是使用物理引擎來(lái)對(duì)運(yùn)動(dòng)物體的摩擦和動(dòng)量來(lái)建模,然而這會(huì)使得計(jì)算過(guò)于復(fù)雜。我們稱這種類型的方程為緩沖函數(shù),幸運(yùn)的是,Core Animation內(nèi)嵌了一系列標(biāo)準(zhǔn)函數(shù)提供給我們使用。

CAMediaTimingFunction

這里傳入如下幾個(gè)常量之一:

  • kCAMediaTimingFunctionLinear
  • kCAMediaTimingFunctionEaseIn
  • kCAMediaTimingFunctionEaseOut
  • kCAMediaTimingFunctionEaseInEaseOut
  • kCAMediaTimingFunctionDefault

kCAMediaTimingFunctionLinear選項(xiàng)創(chuàng)建了一個(gè)線性的計(jì)時(shí)函數(shù),同樣也是CAAnimation的timingFunction屬性為空時(shí)候的默認(rèn)函數(shù)。線性步調(diào)對(duì)于那些立即加速并且保持勻速到達(dá)終點(diǎn)的場(chǎng)景會(huì)有意義(例如射出槍膛的子彈),但是默認(rèn)來(lái)說(shuō)它看起來(lái)很奇怪,因?yàn)閷?duì)大多數(shù)的動(dòng)畫來(lái)說(shuō)確實(shí)很少用到。

kCAMediaTimingFunctionEaseIn常量創(chuàng)建了一個(gè)慢慢加速然后突然停止的方法。對(duì)于之前提到的自由落體的例子來(lái)說(shuō)很適合,或者比如對(duì)準(zhǔn)一個(gè)目標(biāo)的導(dǎo)彈的發(fā)射。

kCAMediaTimingFunctionEaseOut則恰恰相反,它以一個(gè)全速開(kāi)始,然后慢慢減速停止。它有一個(gè)削弱的效果,應(yīng)用的場(chǎng)景比如一扇門慢慢地關(guān)上,而不是砰地一聲。

kCAMediaTimingFunctionEaseInEaseOut創(chuàng)建了一個(gè)慢慢加速然后再慢慢減速的過(guò)程。這是現(xiàn)實(shí)世界大多數(shù)物體移動(dòng)的方式,也是大多數(shù)動(dòng)畫來(lái)說(shuō)最好的選擇。如果只可以用一種緩沖函數(shù)的話,那就必須是它了。那么你會(huì)疑惑為什么這不是默認(rèn)的選擇,實(shí)際上當(dāng)使用UIView的動(dòng)畫方法時(shí),他的確是默認(rèn)的,但當(dāng)創(chuàng)建CAAnimation的時(shí)候,就需要手動(dòng)設(shè)置它了。

最后還有一個(gè)kCAMediaTimingFunctionDefault,它和kCAMediaTimingFunctionEaseInEaseOut很類似,但是加速和減速的過(guò)程都稍微有些慢。它和kCAMediaTimingFunctionEaseInEaseOut的區(qū)別很難察覺(jué),可能是蘋果覺(jué)得它對(duì)于隱式動(dòng)畫來(lái)說(shuō)更適合(然后對(duì)UIKit就改變了想法,而是使用kCAMediaTimingFunctionEaseInEaseOut作為默認(rèn)效果),雖然它的名字說(shuō)是默認(rèn)的,但還是要記住當(dāng)創(chuàng)建顯式的CAAnimation它并不是默認(rèn)選項(xiàng)(換句話說(shuō),默認(rèn)的圖層行為動(dòng)畫用kCAMediaTimingFunctionDefault作為它們的計(jì)時(shí)方法)。

UIView的動(dòng)畫緩沖

UIKit的動(dòng)畫也同樣支持這些緩沖方法的使用,盡管語(yǔ)法和常量有些不同,為了改變UIView動(dòng)畫的緩沖選項(xiàng),給options參數(shù)添加如下常量之一:

  • UIViewAnimationOptionCurveEaseInOut
  • UIViewAnimationOptionCurveEaseIn
  • UIViewAnimationOptionCurveEaseOut
  • UIViewAnimationOptionCurveLinear
    它們和CAMediaTimingFunction緊密關(guān)聯(lián),UIViewAnimationOptionCurveEaseInOut是默認(rèn)值(這里沒(méi)有kCAMediaTimingFunctionDefault相對(duì)應(yīng)的值了)。
//perform the animation
    [UIView animateWithDuration:1.0 delay:0.0
                        options:UIViewAnimationOptionCurveEaseOut
                     animations:^{
                            //set the position
                            self.colorView.center = [[touches anyObject] locationInView:self.view];
                        }
                     completion:NULL];

緩沖和關(guān)鍵幀動(dòng)畫

或許你會(huì)回想起第八章里面顏色切換的關(guān)鍵幀動(dòng)畫由于線性變換的原因(見(jiàn)清單8.5)看起來(lái)有些奇怪,使得顏色變換非常不自然。為了糾正這點(diǎn),我們來(lái)用更加合適的緩沖方法,例如kCAMediaTimingFunctionEaseIn,給圖層的顏色變化添加一點(diǎn)脈沖效果,讓它更像現(xiàn)實(shí)中的一個(gè)彩色燈泡。
我們不想給整個(gè)動(dòng)畫過(guò)程應(yīng)用這個(gè)效果,我們希望對(duì)每個(gè)動(dòng)畫的過(guò)程重復(fù)這樣的緩沖,于是每次顏色的變換都會(huì)有脈沖效果。
CAKeyframeAnimation有一個(gè)NSArray類型的timingFunctions屬性,我們可以用它來(lái)對(duì)每次動(dòng)畫的步驟指定不同的計(jì)時(shí)函數(shù)。但是指定函數(shù)的個(gè)數(shù)一定要等于keyframes數(shù)組的元素個(gè)數(shù)減一,因?yàn)樗敲枋雒恳粠g動(dòng)畫速度的函數(shù)。
在這個(gè)例子中,我們自始至終想使用同一個(gè)緩沖函數(shù),但我們同樣需要一個(gè)函數(shù)的數(shù)組來(lái)告訴動(dòng)畫不停地重復(fù)每個(gè)步驟,而不是在整個(gè)動(dòng)畫序列只做一次緩沖,我們簡(jiǎn)單地使用包含多個(gè)相同函數(shù)拷貝的數(shù)組就可以了。
運(yùn)行更新后的代碼,你會(huì)發(fā)現(xiàn)動(dòng)畫看起來(lái)更加自然了。
對(duì)CAKeyframeAnimation使用CAMediaTimingFunction

 - (IBAction)changeColor {
    //create a keyframe animation
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
    animation.keyPath = @"backgroundColor";
    animation.duration = 2.0;
    animation.values = @[
                         (__bridge id)[UIColor blueColor].CGColor,
                         (__bridge id)[UIColor redColor].CGColor,
                         (__bridge id)[UIColor greenColor].CGColor,
                         (__bridge id)[UIColor blueColor].CGColor ];
    //add timing function
    CAMediaTimingFunction *fn = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseIn];
    animation.timingFunctions = @[fn, fn, fn];
    //apply animation to layer
    [self.colorLayer addAnimation:animation forKey:nil];
}

第2篇:自定義緩沖函數(shù)

在第八章中,我們給時(shí)鐘項(xiàng)目添加了動(dòng)畫。看起來(lái)很贊,但是如果有合適的緩沖函數(shù)就更好了。在顯示世界中,鐘表指針轉(zhuǎn)動(dòng)的時(shí)候,通常起步很慢,然后迅速啪地一聲,最后緩沖到終點(diǎn)。但是標(biāo)準(zhǔn)的緩沖函數(shù)在這里每一個(gè)適合它,那該如何創(chuàng)建一個(gè)新的呢?

除了+functionWithName:之外,CAMediaTimingFunction同樣有另一個(gè)構(gòu)造函數(shù),一個(gè)有四個(gè)浮點(diǎn)參數(shù)的+functionWithControlPoints::::(注意這里奇怪的語(yǔ)法,并沒(méi)有包含具體每個(gè)參數(shù)的名稱,這在objective-C中是合法的,但是卻違反了蘋果對(duì)方法命名的指導(dǎo)方針,而且看起來(lái)是一個(gè)奇怪的設(shè)計(jì))。

使用這個(gè)方法,我們可以創(chuàng)建一個(gè)自定義的緩沖函數(shù)。

Core Animation的插值機(jī)制。這是一個(gè)傳入起點(diǎn)和終點(diǎn),然后在這兩個(gè)點(diǎn)之間指定時(shí)間點(diǎn)產(chǎn)出一個(gè)新點(diǎn)的機(jī)制。對(duì)于簡(jiǎn)單的浮點(diǎn)起始值,公式如下(假設(shè)時(shí)間從0到1):

value = (endValue – startValue) × time + startValue;

那么如果要插入一個(gè)類似于CGPoint,CGColorRef或者CATransform3D這種更加復(fù)雜類型的值,我們可以簡(jiǎn)單地對(duì)每個(gè)獨(dú)立的元素應(yīng)用這個(gè)方法(也就CGPoint中的x和y值,CGColorRef中的紅,藍(lán),綠,透明值,或者是CATransform3D中獨(dú)立矩陣的坐標(biāo))。我們同樣需要一些邏輯在插值之前對(duì)對(duì)象拆解值,然后在插值之后在重新封裝成對(duì)象,也就是說(shuō)需要實(shí)時(shí)地檢查類型。

一旦我們可以用代碼獲取屬性動(dòng)畫的起始值之間的任意插值,我們就可以把動(dòng)畫分割成許多獨(dú)立的關(guān)鍵幀,然后產(chǎn)出一個(gè)線性的關(guān)鍵幀動(dòng)畫。清單10.7展示了相關(guān)代碼。

注意到我們用了60 x 動(dòng)畫時(shí)間(秒做單位)作為關(guān)鍵幀的個(gè)數(shù),這時(shí)因?yàn)镃ore Animation按照每秒60幀去渲染屏幕更新,所以如果我們每秒生成60個(gè)關(guān)鍵幀,就可以保證動(dòng)畫足夠的平滑(盡管實(shí)際上很可能用更少的幀率就可以達(dá)到很好的效果)。

總結(jié):

在這一章中,我們了解了有關(guān)緩沖和CAMediaTimingFunction類,它可以允許我們創(chuàng)建自定義的緩沖函數(shù)來(lái)完善我們的動(dòng)畫,同樣了解了如何用CAKeyframeAnimation來(lái)避開(kāi)CAMediaTimingFunction的限制,創(chuàng)建完全自定義的緩沖函數(shù)。

在下一章中,我們將要研究基于定時(shí)器的動(dòng)畫--另一個(gè)給我們對(duì)動(dòng)畫更多控制的選擇,并且實(shí)現(xiàn)對(duì)動(dòng)畫的實(shí)時(shí)操縱。

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

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