8. 顯式動畫 顯式動畫 如果想讓事情變得順利,只有靠自己 -- 夏爾·紀堯姆 上一章介紹了隱式動畫的概念。隱式動畫是在iOS平臺創建動態用戶界面的一種直接方式,也是UIKit動畫機制的基礎,不過它並不能涵蓋所有的動畫類型。在這一章中,我們將要研究一下顯式動畫,它能夠對一些屬性做指定的自定義動畫,或 ...
8. 顯式動畫
顯式動畫
如果想讓事情變得順利,只有靠自己 -- 夏爾·紀堯姆
上一章介紹了隱式動畫的概念。隱式動畫是在iOS平臺創建動態用戶界面的一種直接方式,也是UIKit動畫機制的基礎,不過它並不能涵蓋所有的動畫類型。在這一章中,我們將要研究一下顯式動畫,它能夠對一些屬性做指定的自定義動畫,或者創建非線性動畫,比如沿著任意一條曲線移動。
8.1 屬性動畫
屬性動畫
CAAnimationDelegate
在任何頭文件中都找不到,但是可以在CAAnimation
頭文件或者蘋果開發者文檔中找到相關函數。在這個例子中,我們用-animationDidStop:finished:
方法在動畫結束之後來更新圖層的backgroundColor
。
當更新屬性的時候,我們需要設置一個新的事務,並且禁用圖層行為。否則動畫會發生兩次,一個是因為顯式的CABasicAnimation
,另一次是因為隱式動畫,具體實現見訂單8.3。
一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個我的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
有另一種方式去指定動畫,就是使用CGPath
。path
屬性可以用一種直觀的方式,使用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
矩陣值。你可以通過設置CAPropertyAnimation
的valueFunction
屬性來改變,於是你設置的函數將會覆蓋預設的函數。
CAValueFunction
看起來似乎是對那些不能簡單相加的屬性(例如變換矩陣)做動畫的非常有用的機制,但由於CAValueFunction
的實現細節是私有的,所以目前不能通過繼承它來自定義。你可以通過使用蘋果目前已近提供的常量(目前都是和變換矩陣的虛擬屬性相關,所以沒太多使用場景了,因為這些屬性都有了預設的實現方式)。
8.2 動畫組
動畫組
CABasicAnimation
和CAKeyframeAnimation
僅僅作用於單獨的屬性,而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
,它創建了一個新的圖層,從邊緣的一側滑動進來,把舊圖層從另一側推出去的效果。
kCATransitionMoveIn
和kCATransitionReveal
與kCATransitionPush
類似,都實現了一個定向滑動的動畫,但是有一些細微的不同,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;
動畫一旦被移除,圖層的外觀就立刻更新到當前的模型圖層的值。一般說來,動畫在結束之後被自動移除,除非設置removedOnCompletion
為NO
,如果你設置動畫在結束之後不被自動移除,那麼當它不需要的時候你要手動移除它;否則它會一直存在於記憶體中,直到圖層被銷毀。
我們來擴展之前旋轉飛船的示例,這裡添加一個按鈕來停止或者啟動動畫。這一次我們用一個非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是怎樣處理逝去的時間。