iOS核心動畫高級技巧-4

8. 顯式動畫

顯式動畫

若是想讓事情變得順利,只有靠本身 -- 夏爾·紀堯姆面試

上一章介紹了隱式動畫的概念。隱式動畫是在iOS平臺建立動態用戶界面的一種直接方式,也是UIKit動畫機制的基礎,不過它並不能涵蓋全部的動畫類型。在這一章中,咱們將要研究一下顯式動畫,它可以對一些屬性作指定的自定義動畫,或者建立非線性動畫,好比沿着任意一條曲線移動。數組

8.1 屬性動畫

屬性動畫

CAAnimationDelegate在任何頭文件中都找不到,可是能夠在CAAnimation頭文件或者蘋果開發者文檔中找到相關函數。在這個例子中,咱們用-animationDidStop:finished:方法在動畫結束以後來更新圖層的backgroundColorapp

當更新屬性的時候,咱們須要設置一個新的事務,而且禁用圖層行爲。不然動畫會發生兩次,一個是由於顯式的CABasicAnimation,另外一次是由於隱式動畫,具體實現見訂單8.3。dom

一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個個人iOS交流羣:1012951431, 分享BAT,阿里面試題、面試經驗,討論技術, 你們一塊兒交流學習成長!但願幫助開發者少走彎路。函數

清單8.3 動畫完成以後修改圖層的背景色佈局

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    //create sublayer
    self.colorLayer = [CALayer layer];
    self.colorLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
    self.colorLayer.backgroundColor = [UIColor blueColor].CGColor;
    //add it to our view
    [self.layerView.layer addSublayer:self.colorLayer];
}

- (IBAction)changeColor
{
    //create a new random color
    CGFloat red = arc4random() / (CGFloat)INT_MAX;
    CGFloat green = arc4random() / (CGFloat)INT_MAX;
    CGFloat blue = arc4random() / (CGFloat)INT_MAX;
    UIColor *color = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
    //create a basic animation
    CABasicAnimation *animation = [CABasicAnimation animation];
    animation.keyPath = @"backgroundColor";
    animation.toValue = (__bridge id)color.CGColor;
    animation.delegate = self;
    //apply animation to layer
    [self.colorLayer addAnimation:animation forKey:nil];
}

- (void)animationDidStop:(CABasicAnimation *)anim finished:(BOOL)flag
{
    //set the backgroundColor property to match animation toValue
    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    self.colorLayer.backgroundColor = (__bridge CGColorRef)anim.toValue;
    [CATransaction commit];
}

@end

 

CAAnimation而言,使用委託模式而不是一個完成塊會帶來一個問題,就是當你有多個動畫的時候,沒法在在回調方法中區分。在一個視圖控制器中建立動畫的時候,一般會用控制器自己做爲一個委託(如清單8.3所示),可是全部的動畫都會調用同一個回調方法,因此你就須要判斷究竟是那個圖層的調用。性能

考慮一下第三章的鬧鐘,「圖層幾何學」,咱們經過簡單地每秒更新指針的角度來實現一個鐘,但若是指針動態地轉向新的位置會更加真實。學習

咱們不能經過隱式動畫來實現由於這些指針都是UIView的實例,因此圖層的隱式動畫都被禁用了。咱們能夠簡單地經過UIView的動畫方法來實現。但若是想更好地控制動畫時間,使用顯式動畫會更好(更多內容見第十章)。使用CABasicAnimation來作動畫可能會更加複雜,由於咱們須要在-animationDidStop:finished:中檢測指針狀態(用於設置結束的位置)。測試

動畫自己會做爲一個參數傳入委託的方法,也許你會認爲能夠控制器中把動畫存儲爲一個屬性,而後在回調用比較,但實際上並不起做用,由於委託傳入的動畫參數是原始值的一個深拷貝,從而不是同一個值。動畫

當使用-addAnimation:forKey:把動畫添加到圖層,這裏有一個到目前爲止咱們都設置爲nil的key參數。這裏的鍵是-animationForKey:方法找到對應動畫的惟一標識符,而當前動畫的全部鍵均可以用animationKeys獲取。若是咱們對每一個動畫都關聯一個惟一的鍵,就能夠對每一個圖層循環全部鍵,而後調用-animationForKey:來比對結果。儘管這不是一個優雅的實現。

幸運的是,還有一種更加簡單的方法。像全部的NSObject子類同樣,CAAnimation實現了KVC(鍵-值-編碼)協議,因而你能夠用-setValue:forKey:-valueForKey:方法來存取屬性。可是CAAnimation有一個不一樣的性能:它更像一個NSDictionary,可讓你隨意設置鍵值對,即便和你使用的動畫類所聲明的屬性並不匹配。

這意味着你能夠對動畫用任意類型打標籤。在這裏,咱們給UIView類型的指針添加的動畫,因此能夠簡單地判斷動畫到底屬於哪一個視圖,而後在委託方法中用這個信息正確地更新鐘的指針(清單8.4)。

清單8.4 使用KVC對動畫打標籤

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIImageView *hourHand;
@property (nonatomic, weak) IBOutlet UIImageView *minuteHand;
@property (nonatomic, weak) IBOutlet UIImageView *secondHand;
@property (nonatomic, weak) NSTimer *timer;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    //adjust anchor points
    self.secondHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f);
    self.minuteHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f);
    self.hourHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f);
    //start timer
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(tick) userInfo:nil repeats:YES];
    //set initial hand positions
    [self updateHandsAnimated:NO];
}

- (void)tick
{
    [self updateHandsAnimated:YES];
}

- (void)updateHandsAnimated:(BOOL)animated
{
    //convert time to hours, minutes and seconds
    NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
    NSUInteger units = NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;
    NSDateComponents *components = [calendar components:units fromDate:[NSDate date]];
    CGFloat hourAngle = (components.hour / 12.0) * M_PI * 2.0;
    //calculate hour hand angle //calculate minute hand angle
    CGFloat minuteAngle = (components.minute / 60.0) * M_PI * 2.0;
    //calculate second hand angle
    CGFloat secondAngle = (components.second / 60.0) * M_PI * 2.0;
    //rotate hands
    [self setAngle:hourAngle forHand:self.hourHand animated:animated];
    [self setAngle:minuteAngle forHand:self.minuteHand animated:animated];
    [self setAngle:secondAngle forHand:self.secondHand animated:animated];
}

- (void)setAngle:(CGFloat)angle forHand:(UIView *)handView animated:(BOOL)animated
{
    //generate transform
    CATransform3D transform = CATransform3DMakeRotation(angle, 0, 0, 1);
    if (animated) {
        //create transform animation
        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];
    } else {
        //set transform directly
        handView.layer.transform = transform;
    }
}

- (void)animationDidStop:(CABasicAnimation *)anim finished:(BOOL)flag
{
    //set final position for hand view
    UIView *handView = [anim valueForKey:@"handView"];
    handView.layer.transform = [anim.toValue CATransform3DValue];
}

 

咱們成功的識別出每一個圖層中止動畫的時間,而後更新它的變換到一個新值,很好。

不幸的是,即便作了這些,仍是有個問題,清單8.4在模擬器上運行的很好,但當真正跑在iOS設備上時,咱們發如今-animationDidStop:finished:委託方法調用以前,指針會迅速返回到原始值,這個清單8.3圖層顏色發生的狀況同樣。

問題在於回調方法在動畫完成以前已經被調用了,但不能保證這發生在屬性動畫返回初始狀態以前。這同時也很好地說明了爲何要在真實的設備上測試動畫代碼,而不只僅是模擬器。

咱們能夠用一個fillMode屬性來解決這個問題,下一章會詳細說明,這裏知道在動畫以前設置它比在動畫結束以後更新屬性更加方便。

關鍵幀動畫

CABasicAnimation揭示了大多數隱式動畫背後依賴的機制,這的確頗有趣,可是顯式地給圖層添加CABasicAnimation相較於隱式動畫而言,只能說費力不討好。

CAKeyframeAnimation是另外一種UIKit沒有暴露出來但功能強大的類。和CABasicAnimation相似,CAKeyframeAnimation一樣是CAPropertyAnimation的一個子類,它依然做用於單一的一個屬性,可是和CABasicAnimation不同的是,它不限制於設置一個起始和結束的值,而是能夠根據一連串隨意的值來作動畫。

關鍵幀起源於傳動動畫,意思是指主導的動畫在顯著改變發生時重繪當前幀(也就是關鍵幀),每幀之間剩下的繪製(能夠經過關鍵幀推算出)將由熟練的藝術家來完成。CAKeyframeAnimation也是一樣的道理:你提供了顯著的幀,而後Core Animation在每幀之間進行插入。

咱們能夠用以前使用顏色圖層的例子來演示,設置一個顏色的數組,而後經過關鍵幀動畫播放出來(清單8.5)

清單8.5 使用CAKeyframeAnimation應用一系列顏色的變化

- (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];
}

 

注意到序列中開始和結束的顏色都是藍色,這是由於CAKeyframeAnimation並不能自動把當前值做爲第一幀(就像CABasicAnimation那樣把fromValue設爲nil)。動畫會在開始的時候忽然跳轉到第一幀的值,而後在動畫結束的時候忽然恢復到原始的值。因此爲了動畫的平滑特性,咱們須要開始和結束的關鍵幀來匹配當前屬性的值。

固然能夠建立一個結束和開始值不一樣的動畫,那樣的話就須要在動畫啓動以前手動更新屬性和最後一幀的值保持一致,就和以前討論的同樣。

咱們用duration屬性把動畫時間從默認的0.25秒增長到2秒,以便於動畫作的不那麼快。運行它,你會發現動畫經過顏色不斷循環,但效果看起來有些奇怪。緣由在於動畫以一個恆定的步調在運行。當在每一個動畫之間過渡的時候並無減速,這就產生了一個略微奇怪的效果,爲了讓動畫看起來更天然,咱們須要調整一下緩衝,第十章將會詳細說明。

提供一個數組的值就能夠按照顏色變化作動畫,但通常來講用數組來描述動畫運動並不直觀。CAKeyframeAnimation有另外一種方式去指定動畫,就是使用CGPathpath屬性能夠用一種直觀的方式,使用Core Graphics函數定義運動序列來繪製動畫。

咱們來用一個宇宙飛船沿着一個簡單曲線的實例演示一下。爲了建立路徑,咱們須要使用一個三次貝塞爾曲線,它是一種使用開始點,結束點和另外兩個控制點來定義形狀的曲線,能夠經過使用一個基於C的Core Graphics繪圖指令來建立,不過用UIKit提供的UIBezierPath類會更簡單。

咱們此次用CAShapeLayer來在屏幕上繪製曲線,儘管對動畫來講並非必須的,但這會讓咱們的動畫更加形象。繪製完CGPath以後,咱們用它來建立一個CAKeyframeAnimation,而後用它來應用到咱們的宇宙飛船。代碼見清單8.6,結果見圖8.1。

清單8.6 沿着一個貝塞爾曲線對圖層作動畫

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *containerView;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    //create a path
    UIBezierPath *bezierPath = [[UIBezierPath alloc] init];
    [bezierPath moveToPoint:CGPointMake(0, 150)];
    [bezierPath addCurveToPoint:CGPointMake(300, 150) controlPoint1:CGPointMake(75, 0) controlPoint2:CGPointMake(225, 300)];
    //draw the path using a CAShapeLayer
    CAShapeLayer *pathLayer = [CAShapeLayer layer];
    pathLayer.path = bezierPath.CGPath;
    pathLayer.fillColor = [UIColor clearColor].CGColor;
    pathLayer.strokeColor = [UIColor redColor].CGColor;
    pathLayer.lineWidth = 3.0f;
    [self.containerView.layer addSublayer:pathLayer];
    //add the ship
    CALayer *shipLayer = [CALayer layer];
    shipLayer.frame = CGRectMake(0, 0, 64, 64);
    shipLayer.position = CGPointMake(0, 150);
    shipLayer.contents = (__bridge id)[UIImage imageNamed: @"Ship.png"].CGImage;
    [self.containerView.layer addSublayer:shipLayer];
    //create the keyframe animation
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
    animation.keyPath = @"position";
    animation.duration = 4.0;
    animation.path = bezierPath.CGPath;
    [shipLayer addAnimation:animation forKey:nil];
}

@end

 

這麼作是可行的,但看起來更由於是運氣而不是設計的緣由,若是咱們把旋轉的值從M_PI(180度)調整到2 * M_PI(360度),而後運行程序,會發現這時候飛船徹底不動了。這是由於這裏的矩陣作了一次360度的旋轉,和作了0度是同樣的,因此最後的值根本沒變。

如今繼續使用M_PI,但此次用byValue而不是toValue。也許你會認爲這和設置toValue結果同樣,由於0 + 90度 == 90度,但實際上飛船的圖片變大了,並無作任何旋轉,這是由於變換矩陣不能像角度值那樣疊加。

那麼若是須要獨立於角度以外單獨對平移或者縮放作動畫呢?因爲都須要咱們來修改transform屬性,實時地從新計算每一個時間點的每一個變換效果,而後根據這些建立一個複雜的關鍵幀動畫,這一切都是爲了對圖層的一個獨立作一個簡單的動畫。

幸運的是,有一個更好的解決方案:爲了旋轉圖層,咱們能夠對transform.rotation關鍵路徑應用動畫,而不是transform自己(清單8.9)。

清單8.9 對虛擬的transform.rotation屬性作動畫

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *containerView;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    //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];
}

@end

 

結果運行的特別好,用transform.rotation而不是transform作動畫的好處以下:

  • 咱們能夠不經過關鍵幀一步旋轉多於180度的動畫。

  • 能夠用相對值而不是絕對值旋轉(設置byValue而不是toValue)。

  • 能夠不用建立CATransform3D,而是使用一個簡單的數值來指定角度。

  • 不會和transform.position或者transform.scale衝突(一樣是使用關鍵路徑來作獨立的動畫屬性)。

transform.rotation屬性有一個奇怪的問題是它其實並不存在。這是由於CATransform3D並非一個對象,它其實是一個結構體,也沒有符合KVC相關屬性,transform.rotation其實是一個CALayer用於處理動畫變換的虛擬屬性。

你不能夠直接設置transform.rotation或者transform.scale,他們不能被直接使用。當你對他們作動畫時,Core Animation自動地根據經過CAValueFunction來計算的值來更新transform屬性。

CAValueFunction用於把咱們賦給虛擬的transform.rotation簡單浮點值轉換成真正的用於擺放圖層的CATransform3D矩陣值。你能夠經過設置CAPropertyAnimationvalueFunction屬性來改變,因而你設置的函數將會覆蓋默認的函數。

CAValueFunction看起來彷佛是對那些不能簡單相加的屬性(例如變換矩陣)作動畫的很是有用的機制,但因爲CAValueFunction的實現細節是私有的,因此目前不能經過繼承它來自定義。你能夠經過使用蘋果目前已近提供的常量(目前都是和變換矩陣的虛擬屬性相關,因此沒太多使用場景了,由於這些屬性都有了默認的實現方式)。

8.2 動畫組

動畫組

CABasicAnimationCAKeyframeAnimation僅僅做用於單獨的屬性,而CAAnimationGroup能夠把這些動畫組合在一塊兒。CAAnimationGroup是另外一個繼承於CAAnimation的子類,它添加了一個animations數組的屬性,用來組合別的動畫。咱們把清單8.6那種關鍵幀動畫和調整圖層背景色的基礎動畫組合起來(清單8.10),結果如圖8.3所示。

清單8.10 組合關鍵幀動畫和基礎動畫

- (void)viewDidLoad
{
    [super viewDidLoad];
    //create a path
    UIBezierPath *bezierPath = [[UIBezierPath alloc] init];
    [bezierPath moveToPoint:CGPointMake(0, 150)];
    [bezierPath addCurveToPoint:CGPointMake(300, 150) controlPoint1:CGPointMake(75, 0) controlPoint2:CGPointMake(225, 300)];
    //draw the path using a CAShapeLayer
    CAShapeLayer *pathLayer = [CAShapeLayer layer];
    pathLayer.path = bezierPath.CGPath;
    pathLayer.fillColor = [UIColor clearColor].CGColor;
    pathLayer.strokeColor = [UIColor redColor].CGColor;
    pathLayer.lineWidth = 3.0f;
    [self.containerView.layer addSublayer:pathLayer];
    //add a colored layer
    CALayer *colorLayer = [CALayer layer];
    colorLayer.frame = CGRectMake(0, 0, 64, 64);
    colorLayer.position = CGPointMake(0, 150);
    colorLayer.backgroundColor = [UIColor greenColor].CGColor;
    [self.containerView.layer addSublayer:colorLayer];
    //create the position animation
    CAKeyframeAnimation *animation1 = [CAKeyframeAnimation animation];
    animation1.keyPath = @"position";
    animation1.path = bezierPath.CGPath;
    animation1.rotationMode = kCAAnimationRotateAuto;
    //create the color animation
    CABasicAnimation *animation2 = [CABasicAnimation animation];
    animation2.keyPath = @"backgroundColor";
    animation2.toValue = (__bridge id)[UIColor redColor].CGColor;
    //create group animation
    CAAnimationGroup *groupAnimation = [CAAnimationGroup animation];
    groupAnimation.animations = @[animation1, animation2]; 
    groupAnimation.duration = 4.0;
    //add the animation to the color layer
    [colorLayer addAnimation:groupAnimation forKey:nil];
}

 

8.3 過渡

過渡

有時候對於iOS應用程序來講,但願能經過屬性動畫來對比較難作動畫的佈局進行一些改變。好比交換一段文本和圖片,或者用一段網格視圖來替換,等等。屬性動畫只對圖層的可動畫屬性起做用,因此若是要改變一個不能動畫的屬性(好比圖片),或者從層級關係中添加或者移除圖層,屬性動畫將不起做用。

因而就有了過渡的概念。過渡並不像屬性動畫那樣平滑地在兩個值之間作動畫,而是影響到整個圖層的變化。過渡動畫首先展現以前的圖層外觀,而後經過一個交換過渡到新的外觀。

爲了建立一個過渡動畫,咱們將使用CATransition,一樣是另外一個CAAnimation的子類,和別的子類不一樣,CATransition有一個type和subtype來標識變換效果。type屬性是一個NSString類型,能夠被設置成以下類型:

kCATransitionFade 
kCATransitionMoveIn 
kCATransitionPush 
kCATransitionReveal

 

到目前爲止你只能使用上述四種類型,但你能夠經過一些別的方法來自定義過渡效果,後續會詳細介紹。

默認的過渡類型是kCATransitionFade,當你在改變圖層屬性以後,就建立了一個平滑的淡入淡出效果。

咱們在第七章的例子中就已經用到過kCATransitionPush,它建立了一個新的圖層,從邊緣的一側滑動進來,把舊圖層從另外一側推出去的效果。

kCATransitionMoveInkCATransitionRevealkCATransitionPush相似,都實現了一個定向滑動的動畫,可是有一些細微的不一樣,kCATransitionMoveIn從頂部滑動進入,但不像推送動畫那樣把老土層推走,然而kCATransitionReveal把原始的圖層滑動出去來顯示新的外觀,而不是把新的圖層滑動進入。

後面三種過渡類型都有一個默認的動畫方向,它們都從左側滑入,可是你能夠經過subtype來控制它們的方向,提供了以下四種類型:

kCATransitionFromRight 
kCATransitionFromLeft 
kCATransitionFromTop 
kCATransitionFromBottom

 

一個簡單的用CATransition來對非動畫屬性作動畫的例子如清單8.11所示,這裏咱們對UIImage的image屬性作修改,可是隱式動畫或者CAPropertyAnimation都不能對它作動畫,由於Core Animation不知道如何在插圖圖片。經過對圖層應用一個淡入淡出的過渡,咱們能夠忽略它的內容來作平滑動畫(圖8.4),咱們來嘗試修改過渡的type常量來觀察其它效果。

清單8.11 使用CATransition來對UIImageView作動畫

@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

 

你能夠從代碼中看出,過渡動畫和以前的屬性動畫或者動畫組添加到圖層上的方式一致,都是經過-addAnimation:forKey:方法。可是和屬性動畫不一樣的是,對指定的圖層一次只能使用一次CATransition,所以,不管你對動畫的鍵設置什麼值,過渡動畫都會對它的鍵設置成「transition」,也就是常量kCATransition

8.4 在動畫過程當中取消動畫

在動畫過程當中取消動畫

以前提到過,你能夠用-addAnimation:forKey:方法中的key參數來在添加動畫以後檢索一個動畫,使用以下方法:

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

 

但並不支持在動畫運行過程當中修改動畫,因此這個方法主要用來檢測動畫的屬性,或者判斷它是否被添加到當前圖層中。

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

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

 

或者移除全部動畫:

- (void)removeAllAnimations;

 

動畫一旦被移除,圖層的外觀就馬上更新到當前的模型圖層的值。通常說來,動畫在結束以後被自動移除,除非設置removedOnCompletionNO,若是你設置動畫在結束以後不被自動移除,那麼當它不須要的時候你要手動移除它;不然它會一直存在於內存中,直到圖層被銷燬。

咱們來擴展以前旋轉飛船的示例,這裏添加一個按鈕來中止或者啓動動畫。這一次咱們用一個非nil的值做爲動畫的鍵,以便以後能夠移除它。-animationDidStop:finished:方法中的flag參數代表了動畫是天然結束仍是被打斷,咱們能夠在控制檯打印出來。若是你用中止按鈕來終止動畫,它會打印NO,若是容許它完成,它會打印YES

清單8.15是更新後的示例代碼,圖8.6顯示告終果。

清單8.15 開始和中止一個動畫

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *containerView;
@property (nonatomic, strong) CALayer *shipLayer;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    //add the ship
    self.shipLayer = [CALayer layer];
    self.shipLayer.frame = CGRectMake(0, 0, 128, 128);
    self.shipLayer.position = CGPointMake(150, 150);
    self.shipLayer.contents = (__bridge id)[UIImage imageNamed: @"Ship.png"].CGImage;
    [self.containerView.layer addSublayer:self.shipLayer];
}

- (IBAction)start
{
    //animate the ship rotation
    CABasicAnimation *animation = [CABasicAnimation animation];
    animation.keyPath = @"transform.rotation";
    animation.duration = 2.0;
    animation.byValue = @(M_PI * 2);
    animation.delegate = self;
    [self.shipLayer addAnimation:animation forKey:@"rotateAnimation"];
}

- (IBAction)stop
{
    [self.shipLayer removeAnimationForKey:@"rotateAnimation"];
}

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
    //log that the animation stopped
    NSLog(@"The animation stopped (finished: %@)", flag? @"YES": @"NO");
}

@end

 

8.5 總結

總結

這一章中,咱們涉及了屬性動畫(你能夠對單獨的圖層屬性動畫有更加具體的控制),動畫組(把多個屬性動畫組合成一個獨立單元)以及過分(影響整個圖層,能夠用來對圖層的任何內容作任何類型的動畫,包括子圖層的添加和移除)。

在第九章中,咱們繼續學習CAMediaTiming協議,來看一看Core Animation是怎樣處理逝去的時間。

相關文章
相關標籤/搜索