CABasicAnimation揭示了大多數隱式動畫背後依賴的機制,這的確頗有趣,可是顯式地給圖層添加CABasicAnimation相較於隱式動畫而言,只能說費力不討好。 git
CAKeyframeAnimation 是另外一種UIKit沒有暴露出來但功能強大的類。和 CABasicAnimation 相似, CAKeyframeAnimation 一樣是 CAPropertyAnimation 的一個子類,它依然做用於單一的一個屬性,可是和 CABasicAnimation 不同的是,它不限制於設置一個起始和結束的值,而是能夠根據一連串隨意的值來作動畫。 github
關鍵幀起源於傳動動畫,意思是指主導的動畫在顯著改變發生時重繪當前幀(也就是關鍵幀),每幀之間剩下的繪製(能夠經過關鍵幀推算出)將由熟練的藝術家來完成。 CAKeyframeAnimation 也是一樣的道理:你提供了顯著的幀,而後Core Animation在每幀之間進行插入。 數組
咱們能夠用以前使用顏色圖層的例子來演示,設置一個顏色的數組,而後經過關鍵幀動畫播放出來(清單8.5) app
清單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)。動畫會在開始的時候忽然跳轉到第一幀的值,而後在動畫結束的時候忽然恢復到原始的值。因此爲了動畫的平滑特性,咱們須要開始和結束的關鍵幀來匹配當前屬性的值。 動畫
固然能夠建立一個結束和開始值不一樣的動畫,那樣的話就須要在動畫啓動以前手動更新屬性和最後一幀的值保持一致,就和以前討論的同樣。 atom
咱們用 duration 屬性把動畫時間從默認的0.25秒增長到2秒,以便於動畫作的不那麼快。運行它,你會發現動畫經過顏色不斷循環,但效果看起來有些奇怪。緣由在於動畫以一個恆定的步調在運行。當在每一個動畫之間過渡的時候並無減速,這就產生了一個略微奇怪的效果,爲了讓動畫看起來更天然,咱們須要調整一下緩衝,第十章將會詳細說明。 spa
提供一個數組的值就能夠按照顏色變化作動畫,但通常來講用數組來描述動畫運動並不直觀。CAKeyframeAnimation有另外一種方式去指定動畫,就是使用CGPath。path屬性能夠用一種直觀的方式,使用Core Graphics函數定義運動序列來繪製動畫。 設計
咱們來用一個宇宙飛船沿着一個簡單曲線的實例演示一下。爲了建立路徑,咱們須要使用一個三次貝塞爾曲線,它是一種使用開始點,結束點和另外兩個控制點來定義形狀的曲線,能夠經過使用一個基於C的Core Graphics繪圖指令來建立,不過用UIKit提供的UIBezierPath類會更簡單。 code
咱們此次用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
圖8.1 沿着一個貝塞爾曲線移動的宇宙飛船圖片
運行示例,你會發現飛船的動畫有些不太真實,這是由於當它運動的時候永遠指向右邊,而不是指向曲線切線的方向。你能夠調整它的affineTransform來對運動方向作動畫,但極可能和其它的動畫衝突。
幸運的是,蘋果預見到了這點,而且給CAKeyFrameAnimation添加了一個rotationMode的屬性。設置它爲常量kCAAnimationRotateAuto(清單8.7),圖層將會根據曲線的切線自動旋轉(圖8.2)。
清單8.7 經過rotationMode自動對齊圖層到曲線
- (void)viewDidLoad { [super viewDidLoad]; //create a path ... //create the keyframe animation CAKeyframeAnimation *animation = [CAKeyframeAnimation animation]; animation.keyPath = @"position"; animation.duration = 4.0; animation.path = bezierPath.CGPath; animation.rotationMode = kCAAnimationRotateAuto; [shipLayer addAnimation:animation forKey:nil]; }
圖8.2 匹配曲線切線方向的飛船圖層
以前提到過屬性動畫其實是針對於關鍵路徑而不是一個鍵,這就意味着能夠對子屬性甚至是虛擬屬性作動畫。可是虛擬屬性究竟是什麼呢?
考慮一個旋轉的動畫:若是想要對一個物體作旋轉的動畫,那就須要做用於transform屬性,由於CALayer沒有顯式提供角度或者方向之類的屬性,代碼如清單8.8所示
清單8.8 用transform屬性對圖層作動畫
@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"; animation.duration = 2.0; animation.toValue = [NSValue valueWithCATransform3D: CATransform3DMakeRotation(M_PI, 0, 0, 1)]; [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作動畫的好處以下:
transform.rotation屬性有一個奇怪的問題是它其實並不存在。這是由於CATransform3D並非一個對象,它其實是一個結構體,也沒有符合KVC相關屬性,transform.rotation其實是一個CALayer用於處理動畫變換的虛擬屬性。
你不能夠直接設置transform.rotation或者transform.scale,他們不能被直接使用。當你對他們作動畫時,Core Animation自動地根據經過CAValueFunction來計算的值來更新transform屬性。
CAValueFunction用於把咱們賦給虛擬的transform.rotation簡單浮點值轉換成真正的用於擺放圖層的CATransform3D矩陣值。你能夠經過設置CAPropertyAnimation的valueFunction屬性來改變,因而你設置的函數將會覆蓋默認的函數。
CAValueFunction看起來彷佛是對那些不能簡單相加的屬性(例如變換矩陣)作動畫的很是有用的機制,但因爲CAValueFunction的實現細節是私有的,因此目前不能經過繼承它來自定義。你能夠經過使用蘋果目前已近提供的常量(目前都是和變換矩陣的虛擬屬性相關,因此沒太多使用場景了,由於這些屬性都有了默認的實現方式)。