1.CATransaction併發
事務;app
UIView有兩個方法,+beginAnimations:context:和+commitAnimations,和CATransaction的+begin 和+commit方法相似。實際上在+beginAnimations:context:和+commitAnimations之間全部視圖或者圖層屬性的改變而作的動畫都是因爲設置了CATransaction的緣由。dom
UIView封裝動畫中的塊動畫,對作一堆的屬性動畫在語法上會更加簡單,但實質上它們都是在作一樣的事情。ide
基於UIView的block的動畫容許你在動畫結束的時候提供一個完成的動做。CATranscation接口提供的+setCompletionBlock:方法也有一樣的功能函數
2.屬性動畫實質:測試
當CALayer的屬性被修改時候,它會調用-actionForKey:方法,傳遞屬性的名稱。剩下的操做都在CALayer的頭文件中有詳細的說明,實質上是以下幾步:動畫
圖層首先檢測它是否有委託,而且是否實現CALayerDelegate協議指定的-actionForLayer:forKey方法。若是有,直接調用並返回結果。this
若是沒有委託,或者委託沒有實現-actionForLayer:forKey方法,圖層接着檢查包含屬性名稱對應行爲映射的actions字典。atom
若是actions字典沒有包含對應的屬性,那麼圖層接着在它的style字典接着搜索屬性名。url
最後,若是在style裏面也找不到對應的行爲,那麼圖層將會直接調用定義了每一個屬性的標準行爲的-defaultActionForKey:方法。
因此一輪完整的搜索結束以後,-actionForKey:要麼返回空(這種狀況下將不會有動畫發生),要麼是CAAction協議對應的對象,最後CALayer拿這個結果去對先前和當前的值作動畫。
經過下面一段代碼,能夠理解每一個UIView對它關聯的圖層是如何禁用隱式動畫的
- (void)viewDidLoad { [super viewDidLoad]; NSLog(@"Outside: %@", [self.view actionForLayer:self.view.layer forKey:@"backgroundColor"]); [UIView beginAnimations:nil context:nil]; NSLog(@"Inside: %@", [self.view actionForLayer:self.view.layer forKey:@"backgroundColor"]); [UIView commitAnimations]; }
運行結果:
2016-05-30 14:48:18.614 測試[10222:147396] Outside: <null> 2016-05-30 14:48:18.615 測試[10222:147396] Inside: <CABasicAnimation: 0x7f8f02403d10>
總結一下,咱們知道了以下幾點
UIView 關聯的圖層禁用了隱式動畫,對這種圖層作動畫的惟一辦法就是使用UIView的動畫函數(而不是依賴CATransaction),或者繼承 UIView,並覆蓋-actionForLayer:forKey:方法,或者直接建立一個顯式動畫(具體細節見第八章)。
對於單獨存在的圖層,咱們能夠經過實現圖層的-actionForLayer:forKey:委託方法,或者提供一個actions字典來控制隱式動畫。
3.CATransition
CATransition繼承CAAnimation,而CAAnimation響應CAAction協議;
CATransition *transition = [CATransition animation]; transition.type = kCATransitionPush; transition.subtype = kCATransitionFromLeft; self.colorLayer.actions = @{@"backgroundColor": transition};
這種方法,只是針對單獨的圖層,不能用在UIView關聯的圖層;
4.presentationLayer
每一個圖層屬性的顯示值都被存儲在一個叫作呈現圖層的獨立圖層當中,他能夠經過-presentationLayer方法來訪問。這個呈現圖層其實是模型圖層的複製,可是它的屬性值表明了在任何指定時刻當前外觀效果。換句話說,你能夠經過呈現圖層的值來獲取當前屏幕上真正顯示出來的值
注意呈現圖層僅僅當圖層首次被提交(就是首次第一次在屏幕上顯示)的時候建立,因此在那以前調用-presentationLayer將會返回nil。
在呈現圖層上調用–modelLayer將會返回它正在呈現所依賴的CALayer。一般在一個圖層上調用-modelLayer會返回–self(實際上咱們已經建立的原始圖層就是一種數據模型)。
5.顯式動畫
在iOS中核心動畫分爲幾類:基礎動畫、關鍵幀動畫、動畫組、轉場動畫。各個類的關係大體以下:
CAAnimation:核心動畫的基礎類,不能直接使用,負責動畫運行時間、速度的控制。CAAnimation自己並無作多少工做,它提供了一個計時函數,一個委託(用於反饋動畫狀態)以及一個 removedOnCompletion,用於標識動畫是否該在結束後自動釋放(默認YES,爲了防止內存泄露)。CAAnimation同時實現了一些 協議,包括CAAction(容許CAAnimation的子類能夠提供圖層行爲),以及CAMediaTiming
CAPropertyAnimation:屬性動畫的基類(經過屬性進行動畫設置,注意是可動畫屬性),不能直接使用。經過指定動畫的keyPath做用於一個單一屬性,CAAnimation一般應用於一個指定的CALayer,因而這裏指的也就是一個圖層的 keyPath了。實際上它是一個關鍵路徑(一些用點表示法能夠在層級關係中指向任意嵌套的對象),而不只僅是一個屬性的名稱,由於這意味着動畫不只能夠 做用於圖層自己的屬性,並且還包含了它的子成員的屬性,甚至是一些虛擬的屬性
CAAnimationGroup:動畫組,動畫組是一種組合模式設計,能夠經過動畫組來進行全部動畫行爲的統一控制,組中全部動畫效果能夠併發執行。
CATransition:過渡(轉場)動畫,主要經過濾鏡進行動畫效果設置。過渡並不像屬性動畫那樣平滑地在兩個值之間作動畫,而是影響到整個圖層的變化。過渡動畫首先展現以前的圖層外觀,而後經過一個交換過渡到新的外觀。
CABasicAnimation:基礎動畫,經過屬性修改進行動畫參數控制,只有初始狀態和結束狀態。
CAKeyframeAnimation:關鍵幀動畫,一樣是經過屬性進行動畫參數控制,可是同基礎動畫不一樣的是它能夠有多個狀態控制。CAKeyFrameAnimation添加了一個rotationMode的屬性。設置它爲常量kCAAnimationRotateAuto,圖層將會根據曲線的切線自動旋轉
基礎動畫、關鍵幀動畫都屬於屬性動畫,就是經過修改屬性值產生動畫效果,開發人員只須要設置初始值和結束值,中間的過程動畫(又叫「補間動畫」)由 系統自動計算產生。和基礎動畫不一樣的是關鍵幀動畫能夠設置多個屬性值,每兩個屬性中間的補間動畫由系統自動完成,所以從這個角度而言基礎動畫又能夠當作是 有兩個關鍵幀的關鍵幀動畫。
- (void)applyBasicAnimation:(CABasicAnimation *)animation toLayer:(CALayer *)layer { //set the from value (using presentation layer if available) animation.fromValue = [layer.presentationLayer ?: layer valueForKeyPath:animation.keyPath]; //update the property in advance //note: this approach will only work if toValue != nil [CATransaction begin]; [CATransaction setDisableActions:YES]; [layer setValue:animation.toValue forKeyPath:animation.keyPath]; [CATransaction commit]; //apply animation to layer [layer addAnimation:animation forKey:nil]; } - (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; //apply animation without snap-back [self applyBasicAnimation:animation toLayer:self.colorLayer]; }
6.虛擬屬性
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作動畫的好處以下:
咱們能夠不經過關鍵幀一步旋轉多於180度的動畫。
能夠用相對值而不是絕對值旋轉(設置byValue而不是toValue)。
能夠不用建立CATransform3D,而是使用一個簡單的數值來指定角度。
不會和transform.position或者transform.scale衝突(一樣是使用關鍵路徑來作獨立的動畫屬性)。
transform.rotation 屬性有一個奇怪的問題是它其實並不存在。這是由於CATransform3D並非一個對象,它其實是一個結構體,也沒有符合KVC相關屬 性,transform.rotation其實是一個CALayer用於處理動畫變換的虛擬屬性。
7.動畫組
//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.過渡動畫
CAAnimation有一個type和subtype來標識變換效果。type屬性是一個NSString類型,能夠被設置成以下類型:
kCATransitionFade
kCATransitionMoveIn
kCATransitionPush
kCATransitionReveal
經過subtype來控制它們的方向,提供了以下四種類型:
kCATransitionFromRight
kCATransitionFromLeft
kCATransitionFromTop
kCATransitionFromBottom
隱式過渡
CATransision 能夠對圖層任何變化平滑過渡的事實使得它成爲那些很差作動畫的屬性圖層行爲的理想候選。蘋果固然意識到了這點,而且當設置了CALayer的 content屬性的時候,CATransition的確是默認的行爲。可是對於視圖關聯的圖層,或者是其餘隱式動畫的行爲,這個特性依然是被禁用的,但 是對於你本身建立的圖層,這意味着對圖層contents圖片作的改動都會自動附上淡入淡出的動畫。
對圖層樹的動畫
CATransition並不做用於指定的圖層屬性,這就是說你能夠在即便不能準確得知改變了什麼的狀況下對圖層作動畫,例如,在不知道 UITableView哪一行被添加或者刪除的狀況下,直接就能夠平滑地刷新它,或者在不知道UIViewController內部的視圖層級的狀況下對 兩個不一樣的實例作過渡動畫。
這些例子和咱們以前所討論的狀況徹底不一樣,由於它們不涉及到圖層的屬性,並且是整個圖層樹的改變--咱們在這種動畫的過程當中手動在層級關係中添加或者移除圖層。
這裏用到了一個小詭計,要確保CATransition添加到的圖層在過渡動畫發生時不會在樹狀結構中被移除,不然CATransition將會和圖層一塊兒被移除。通常來講,你只須要將動畫添加到被影響圖層的superlayer。
- (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]; }
自定義動畫
奇怪的是蘋果經過UIView +transitionFromView:toView:duration:options:completion: 和+transitionWithView:duration:options:animations:方法提供了Core Animation的過渡特性。可是這裏的可用的過渡選項和CATransition的type屬性提供的常量徹底不一樣。UIView過渡方法中 options參數能夠由以下常量指定:
UIViewAnimationOptionTransitionFlipFromLeft
UIViewAnimationOptionTransitionFlipFromRight
UIViewAnimationOptionTransitionCurlUp
UIViewAnimationOptionTransitionCurlDown
UIViewAnimationOptionTransitionCrossDissolve
UIViewAnimationOptionTransitionFlipFromTop
UIViewAnimationOptionTransitionFlipFromBottom
過渡動畫作基礎的原則就是對原始的圖層外觀截圖,而後添加一段動畫,平滑過渡到圖層改變以後那個截圖的效果。若是咱們知道如何對圖層截圖,咱們就可使用屬性動畫來代替CATransition或者是UIKit的過渡方法來實現動畫。
事實證實,對圖層作截圖仍是很簡單的。CALayer有一個-renderInContext:方法,能夠經過把它繪製到Core Graphics的上下文中捕獲當前內容的圖片,而後在另外的視圖中顯示出來。若是咱們把這個截屏視圖置於原始視圖之上,就能夠遮住真實視圖的全部變化, 因而從新建立了一個簡單的過渡效果。
清單8.14演示了一個基本的實現。咱們對當前視圖狀態截圖,而後在咱們改變原始視圖的背景色的時候對截圖快速轉動而且淡出,圖8.5展現了咱們自定義的過渡效果。
爲 了讓事情更簡單,咱們用UIView -animateWithDuration:completion:方法來實現。雖然用CABasicAnimation能夠達到一樣的效果,可是那樣的 話咱們就須要對圖層的變換和不透明屬性建立單獨的動畫,而後當動畫結束的是哦戶在CAAnimationDelegate中把coverView從屏幕中 移除。
清單8.14 用renderInContext:建立自定義過渡效果
- (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]; }]; }
CAMediaTiming協議
duration
repeatCount
repeatDuration
autoreverses
注意repeatCount和repeatDuration可能會相互衝突,因此你只要對其中一個指定非零值。對兩個屬性都設置非0值的行爲沒有被定義。
beginTime
指定了動畫開始以前的的延遲時間。這裏的延遲從動畫添加到可見圖層的那一刻開始測量,默認是0(就是說動畫會馬上執行)。
speed
是一個時間的倍數,默認1.0,減小它會減慢圖層/動畫的時間,增長它會加快速度。若是2.0的速度,那麼對於一個duration爲1的動畫,實際上在0.5秒的時候就已經完成了。
timeOffset
和beginTime相似,可是和增長beginTime致使的延遲動畫不一樣,增長timeOffset只是讓動畫快進到某一點,例如,對於一個持續1秒的動畫來講,設置timeOffset爲0.5意味着動畫將從一半的地方開始。
fillMode
fillMode是一個NSString類型,能夠接受以下四種常量:
kCAFillModeForwards
kCAFillModeBackwards
kCAFillModeBoth
kCAFillModeRemoved
默認是kCAFillModeRemoved,當動畫再也不播放的時候就顯示圖層模型指定的值剩下的三種類型向前,向後或者即向前又向後去填充動畫狀態,使得動畫在開始前或者結束後仍然保持開始和結束那一刻的值。
這就對避免在動畫結束的時候急速返回提供另外一種方案(見第八章)。可是記住了,當用它來解決這個問題的時候,須要把removeOnCompletion設置爲NO,另外須要給動畫添加一個非空的鍵,因而能夠在不須要動畫的時候把它從圖層上移除。
暫停,倒回和快進(圖層的speed)
設置動畫的speed屬性爲0能夠暫停動畫,但在動畫被添加到圖層以後不太可能再修改它了,因此不能對正在進行的動畫使用這個屬性。給圖層添加一個 CAAnimation其實是給動畫對象作了一個不可改變的拷貝,因此對原始動畫對象屬性的改變對真實的動畫並無做用。相反,直接用 -animationForKey:來檢索圖層正在進行的動畫能夠返回正確的動畫對象,可是修改它的屬性將會拋出異常。
若是移除圖層正在進行的動畫,圖層將會急速返回動畫以前的狀態。但若是在動畫移除以前拷貝呈現圖層到模型圖層,動畫將會看起來暫停在那裏。可是很差的地方在於以後就不能再恢復動畫了。
一個簡單的方法是能夠利用CAMediaTiming來暫停圖層自己。若是把圖層的speed設置成0,它會暫停任何添加到圖層上的動畫。相似的,設置speed大於1.0將會快進,設置成一個負值將會倒回動畫。
經過增長主窗口圖層的speed,能夠暫停整個應用程序的動畫。這對UI自動化提供了好處,咱們能夠加速全部的視圖動畫來進行自動化測試(注意對於在主窗口以外的視圖並不會被影響,好比UIAlertview)。能夠在app delegate設置以下進行驗證:
self.window.layer.speed = 100;
你也能夠經過這種方式來減速,但其實也能夠在模擬器經過切換慢速動畫來實現。
手動動畫(圖層的timeOffset)
timeOffset一個頗有用的功能在於你能夠它可讓你手動控制動畫進程,經過設置speed爲0,能夠禁用動畫的自動播放,而後來使用timeOffset來來回顯示動畫序列。這可使得運用手勢來手動控制動畫變得很簡單。
@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:@"bjl_list_02"].CGImage; [self.view.layer addSublayer:self.doorLayer]; //apply perspective transform CATransform3D perspective = CATransform3DIdentity; perspective.m34 = -1.0 / 500.0; self.view.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]; }