iOS动画-CAAnimation使用详解
理解了隱式動畫后,顯式動畫就更加通俗易懂了。區別于隱式動畫的特點,顯式動畫就是需要我們明確指定類型、時間等參數來實現效果的動畫。除此之外,我們也可以創建非線性動畫,比如沿著任意一條曲線運動等;
我們平時最常用的也是顯式動畫,不僅系統為我們的視圖提供了UIViewAnimationWithBlock的動畫封裝,而且我們在熟悉了Core Animation的動畫屬性后也可以很方便的設置顯式動畫;
本篇主要內容:
1.iOS動畫的分類
2.CAMediaTiming協議
3.CAAnimation基類
4.CAPropertyAnimation基類
5.基礎動畫CABasicAnimation
6.關鍵幀動畫CAKeyframeAnimation
7.動畫組CAGroupAnimation
8.過渡動畫CATransition
9.委托模式下的動畫區分
10.虛擬屬性及其作用
11.動畫的取消
相關文章:
iOS動畫-CALayer寄宿圖與繪制原理
iOS動畫-CALayer布局屬性詳解
iOS動畫-CALayer隱式動畫原理與特性
iOS動畫-CAAnimation使用詳解
一、動畫的分類
1、實現動畫的方式
如果根據實現動畫時直接操作對象的類型,我們可以簡單的將動畫分為視圖和圖層兩種;但事實上,無論UIViewAnimaiton動畫還是UIViewAnimaitonWithBlock動畫都只是對UIView的關聯圖層CALayer動畫的進一步封裝。
實現動畫的方式.png
2.核心動畫Core Animation常用類的繼承關系
我們在使用Core Animation動畫之前,有必要對核心動畫常見的類和動畫屬性做一個基本了解;從繼承關系的圖示中,我們可以十分清晰的看出這些屬性設置設置因何而來,以及它們各自的聯系。
核心動畫類的繼承關系.jpg
| CAMediaTiming | 協議;定義了一段動畫內用于控制時間的屬性的集合 |
| CAAnimation | 抽象類;作為所有動畫類型父類,不可直接使用 |
| CAPropertyAnimation | 抽象類;作為基礎動畫和幀動畫的父類,不可直接使用 |
| CABasicAnimation | 基礎動畫;用于實現單一屬性變化的動畫 |
| CAKeyFrameAnimation | 關鍵幀動畫;用于實現單一屬性連續變化的動畫 |
| CAAnimaitionGroup | 組動畫;用于實現多屬性同時變化的動畫 |
| CATrasition | 轉場過渡動畫; |
二、CAMediaTiming協議
CAMediaTiming協議定義了一段動畫內用于控制時間的屬性的集合,CALayer和CAAnimation都實現了這個協議,所以時間可以被任意基于一個圖層或者一段動畫的類控制,有關CAMediaTimg協議具體的屬性如下:
| beginTime | CFTimeInterval | 動畫開始之前的延遲時間,這里的延遲從動畫添加到可見圖層上那一刻開始測量; (設置動畫beginTime為1,動畫將延時1秒后開始執行) |
| duration | CFTimeInterval | 動畫持續時間; (默認值為0,但是實際動畫默認持續時間為0.25秒) |
| speed | float | 動畫執行的速度; (默認值為0,減少它會減慢動畫的時間,增加它會加快速度) (設置speed為2時,則動畫實際執行時間是duration的一半) |
| timeOffset | CFTimeInterval | 動畫時間偏移量; (設置時長3秒動畫的timeOffset為1時,動畫會從1秒位置執到最后,再執行之前跳過的部分) |
| repeatCount | float | 動畫重復次數;默認值是0,但是實際默認動畫執行1次; (設置為INFINITY,則一直執行); (duration是2,repeatCount設置為3.5,則完整動畫時長7秒) |
| repeatDuration | CFTimeInterval | 動畫重復的時間,讓動畫重復執行一個指定的時間; (設置為INFINITY,一直執行) repeatCount和repeatDuration可能會相互沖突,所以你只需要對其中一個指定非零值,對兩個屬性都設置非0值的行為沒有被定義; |
| autoreverses | BOOL | 動畫從初始值執行到最終值,是否會反向回到初始值; (設置為YES,動畫完成后將以動畫的形式回到初始位置) |
| fillMode | NSStrinng | 決定當前對象在非動畫時間端段的動畫屬性值,如動畫開始之前和動畫結束之后 |
1.fillMode詳細說明
試想這樣一個問題:在beginTime非0(即動畫未真正執行之前),以及removeOnCompletion被設置為NO的動畫結束時,我們會遇到這樣一個問題:被設置動畫的屬性應該是什么值?
一種可能是屬性與動畫沒被添加之前保持一致,還有一種可能是保持動畫開始之前那一幀或者動畫結束那一幀,這就是所謂的填充。
CAMediaTiming的fillMode用來控制填充效果,它是一個NSString類型,有四種常量可供使用:
| kCAFillModeRemoved (default) | NSString | 默認值,動畫開始前和結束后,動畫對圖層都沒有影響,圖層依然保持初始值 |
| kCAFillModeForwards | NSString | 動畫結束后,圖層一直保持動畫后的最終狀態 |
| kCAFillModeBackwards | NSString | 動畫開始前,只要加入動畫就會處于動畫的初始狀態 |
| kCAFillModeBoth | NSString | 綜合了kCAFillModeForwards與kCAFillModeBackwards特性; (動畫加入圖層到真正執行動畫的時間段里,圖層保持動畫初始狀態;動畫結束之后保持動畫最終狀態) |
特別注意:removedOnCompletion需要設置為NO,否則fillMode不起作用;
2.CAMediaTiming屬性應用總結
時間屬性的綜合應用.png
三、CAAnimation基類
CAAnimation作為所有動畫類型父類,是一個抽象類;我們不能直接使用CAAnimation類,而是使用它的子類;關于它的定義如下:
@interface CAAnimation : NSObject<NSSecureCoding, NSCopying, CAMediaTiming, CAAction>@property(nullable, strong) CAMediaTimingFunction *timingFunction; @property(nullable, strong) id <CAAnimationDelegate> delegate; @property(getter=isRemovedOnCompletion) BOOL removedOnCompletion;@end可以看到,CAAnimation動畫基類遵循了CAMediaTiming協議,而且另外包含了三個常用的動畫屬性;下面是對這三個屬性的總結:
1.動畫緩沖屬性timingFunction
動畫實際上就是在一段時間內隨著某個特定速率執行變化的過程,現實中的任何物體都會在運動中經歷加速或者減速的過程,而不是速度驟變;因此,CoreAnimation也內嵌了一系列標準的緩沖函數來使動畫看起來更平滑自然,這就是我們要說到的動畫緩沖。
timingFunction屬性是CAMediaTimingFunction類的一個對象,用來控制圖層動畫變換的速度;使用它需要調用+functionWithName:的構造方法,下面是可傳入的變量的介紹:
| KCAMediaTimingFuncationLinear | 默認,勻速執行動畫 |
| KCAMediaTimingFuncationEaseIn | 先慢慢加速,后突然停止 |
| KCAMediaTimingFuncationEaseOut | 先全速開始,再慢慢減速停止 |
| KCAMediaTimingFuncationEaseInEaseOut | 先慢慢加速,再慢慢減速 |
| KCAMediaTimingFuncationDefault | 效果同KCAMediaTimingFuncationEaseInEaseOut |
這五種不同的緩沖效果如下:
動畫緩沖屬性timingFunction.jpg
通過這種方法控制動畫速度,其實是使用不同的變量創建了不同的計時函數。比如KCAMediaTimingFuncationLinear選項創建的是一個線性的計時函數,這也是CAAnimation的timingFunction屬性為空時候的默認函數。
注意:KCAMediaTimingFuncationDefault相比KCAMediaTimingFuncationEaseInEaseOut的加速和減速過程稍微有些慢,兩者區別很難察覺;可能蘋果也覺得它更適合用于隱式動畫,就作為了隱式動畫的默認效果;但是創建顯式的CAAnimation時,KCAMediaTimingFuncationLinear才是默認效果而非KCAMediaTimingFuncationDefault;
UIKit動畫其實也同樣支持這些緩沖效果的使用,在我們使用UIViewAnimationBlock實現動畫的時候,可以給options參數提供了如下的常量來修改緩沖效果:
| UIViewAnimationOptionCurveLinear | 默認,勻速執行動畫 |
| UIViewAnimationOptionCurveEaseIn | 先慢慢加速,后突然停止 |
| UIViewAnimationOptionCurveEaseOut | 先全速開始,再慢慢減速停止 |
| UIViewAnimationOptionCurveEaseInOut | 先慢慢加速,再慢慢減速 |
2.動畫代理屬性delegate
/* Delegate methods for CAAnimation. */ @protocol CAAnimationDelegate <NSObject>@optional //動畫開始時調用 - (void)animationDidStart:(CAAnimation *)anim; //動畫結束時調用 - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag; @end3.removedOnCompletion
removedOnCompletion屬性默認為YES,表示動畫完成后就會從圖層上移除,圖層也會恢復到動畫執行前的狀態;當其修改為NO時,那么圖層將會保持動畫結束后的狀態,此時的fillMode屬性也將生效;
另外,removedOnCompletion設置為NO時,直到我們手動移除動畫,否則動畫將不會自動釋放;所以通常我們此時會給動畫添加一個非空的鍵,這樣可以在不需要動畫的時候把它從圖層上移除;
當我們需要防止動畫結束后回到初始狀態時,
只需設置removedOnCompletion、fillMode兩個屬性就可以了。
transformAnima.removedOnCompletion = NO; transformAnima.fillMode = kCAFillModeForwards;解釋:為什么動畫結束后返回原狀態?
首先我們需要搞明白一點的是,layer動畫運行的過程是怎樣的?其實在我們給一個視圖添加layer動畫時,真正移動并不是我們的視圖本身,而是 presentation layer 的一個緩存。動畫開始時 presentation layer開始移動,原始layer隱藏,動畫結束時,presentation layer從屏幕上移除,原始layer顯示。這就解釋了為什么我們的視圖在動畫結束后又回到了原來的狀態,因為它根本就沒動過。
這個同樣也可以解釋為什么在動畫移動過程中,我們為何不能對其進行任何操作。我們點擊動畫不能響應事件,因為移動的只是layer, view還是在原始的位置.
所以在我們完成layer動畫之后,最好將我們的layer屬性設置為我們最終狀態的屬性,然后將presentation layer 移除掉。
四、CAPropertyAnimation基類
CAPropertyAnimation是一個抽象類,不能直接用于實現CALayer動畫操作,但是它的類定義中增加用于設置CALayer可被實現動畫的屬性keyPath,總結這些屬性如下:
| transform.rotation | 默認圍繞z軸旋轉,相當于transform.rotation.z |
| transform.rotation.x transform.rotation.y transform.rotation.z | 分別圍繞x軸、y軸、z軸旋轉; |
| transform.scale | 在所有方向上進行縮放 |
| transform.scale.x transform.scale.y transform.scale.z | 分別在x軸、y軸、z軸方向上縮放; |
| transform.translation | 平移到指定坐標點 |
| transform.translation.x transform.translation.y transform.translation.z | 分別在x軸、y軸、z軸方向上平移; |
| zPosition | z軸位置 |
| opacity | 透明度 |
| backgroundColor | 背景顏色 |
| cornerRadius | 圓角大小 |
| borderWidth | 邊框寬度 |
| bounds | 圖層大小 |
| contents | 寄宿圖內容 |
| contentsRect | 可視內容 |
| position | 圖層位置,類似transform.translation |
| shadowColor | 陰影顏色 |
| shadowOffset | 陰影偏移 |
| shadowOpacity | 陰影透明度 |
| shadowRadius | 陰影角度 |
附:KeyPath官方參考鏈接
五、基礎動畫CABasicAnimation
CABasicAnimation即基礎動畫,在指定可動畫屬性后,動畫會按照預定的參數持續一定時間由初始值變換為終點值。其實,CABasicAnimation就相當于只有開始和結束兩個幀的特殊關鍵幀動畫(后續會詳解);
1.屬性說明
| fromValue | 起始值 |
| toValue | 結束值 |
| byValue | keyPath屬性的變化值 |
2.動畫演示
下面的示例使用CABasicAnimation實現了修改顏色圖層colorLayer的背景色為隨機顏色的動畫,具體的代碼如下:
@interface TestBacicAnimation1VC ()<CAAnimationDelegate> @property (nonatomic,strong) CALayer *colorLayer; @end@implementation TestBacicAnimation1VC- (void)viewDidLoad {[super viewDidLoad];//創建顯示顏色的圖層,添加于視圖控制器的View上CALayer *colorLayer = [CALayer layer];colorLayer.frame = CGRectMake(50, 50, 100, 100);colorLayer.backgroundColor = [UIColor redColor].CGColor;self.colorLayer = colorLayer;[self.view.layer addSublayer:colorLayer]; }- (IBAction)changeColor:(UIButton *)sender{//步驟1:創建動畫CABasicAnimation *animation = [CABasicAnimation animation];animation.keyPath = @"backgroundColor";//步驟2:設定動畫屬性animation.autoreverses = NO;animation.duration = 0.25;animation.repeatCount = 1;animation.removedOnCompletion = NO;animation.fillMode = kCAFillModeForwards;animation.delegate = self;UIColor *randomColor = [UIColor randomColor]; //自定義獲取隨機色的方法animation.toValue = (__bridge id _Nullable)(randomColor.CGColor);//步驟3:添加動畫到圖層[self.colorLayer addAnimation:animation forKey:@"keyPath_backgroundColor"]; }- (void)animationDidStop:(CABasicAnimation *)anim finished:(BOOL)flag{//禁用隱式動畫[CATransaction begin];[CATransaction setDisableActions:true];self.colorLayer.backgroundColor = (__bridge CGColorRef)anim.toValue;[CATransaction commit]; }效果圖如下:
CABasicAnimation.gif
總結創建動畫的兩種方式如下:
//方法1:實例化同時指定動畫類型 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"backgroundColor"];//方法2:先實例化,再指定動畫類型 CABasicAnimation *animation = [CABasicAnimation animation]; animation.keyPath = @"backgroundColor";3.關閉隱式動畫
對獨立圖層(即非UIView的關聯圖層,類似上述例子中的colorLayer)做更新屬性的顯式動畫,我們需要設置一個事務來禁用圖層行為,否則動畫會發生兩次,一次是因為顯式的CABasicAnimation,另一次是因為隱式動畫,從而導致我們看到的動畫異常。
補充--彈簧動畫使用
iOS 彈簧動畫詳解CASpringAnimation
?
六、關鍵幀動畫CAKeyframeAnimation
CACAKeyfameAnimation是CAPropertyAnimation的另一個子類,它和和CABasicAnimation一樣都只能作用于圖層對象的單一屬性;它們的區別在于:CACAKeyfameAnimation不限制于設置一個起始值和結束值,而是可以根據一連串的值來做動畫。其實,CABasicAnimation可看做是只有2個關鍵幀的CAKeyframeAnimation。
1.關鍵幀動畫常用屬性總結
關鍵幀動畫相對于基礎動畫的具有一些獨特的屬性,我們現將其總結如下:
| values | 用于提供關鍵幀數據的數組,數組中每一個值都對應一個關鍵幀屬性值; 數組中的數據類型根據動畫類型(KeyPath)而不同; 當使用path的時候,values的值將會被自動忽略; |
| path | 用于提供關鍵幀數據的路徑; path與values屬性作用相同,但是兩者互斥,同時指定values和path,path會覆蓋values的效果; |
| keyTimes | ktyTimes與Values中的值具有一一對應的關系,用于指定關鍵幀在動畫的時間點,取值范圍是[0,1]; 若沒有設置keyTimes,則每個關鍵幀的時間是平分動畫總時長(duration); |
| timingFunctions | 用于指定每個關鍵幀之間的動畫緩沖效果,這類似于物體運動的加速度; 注意:存在幾個子路徑就應該在此數組中傳入幾個元素; |
| calculationMode | 該屬性決定了物體在每個子路徑下是跳著走還是勻速走,跟timeFunctions屬性有點類似; |
| rotationMode | 設置幀動畫是否需要按照路徑切線的方向運動; |
2.實現幀動畫:使用values
從關鍵幀動畫的屬性可以看出,我們可以總結出關鍵幀動畫的實現方式實際分為兩種:
1.通過values設置關鍵幀屬性值數組;
2.通過path設置關鍵幀路徑,而且此種方式的優先級較高;
這里首先測試第一種方式,實現這樣的關鍵幀動畫:創建一個紫色滑塊在四個坐標點之間滑動;具體的代碼實現如下:
關鍵幀動畫效果如下:
CAKeyframeAnimation_values.gif
3.實現關鍵幀動畫:使用path
現在,我們測試CAKeyframeAnimation使用path實現這樣一個動畫:一架飛機沿著一個簡單的曲線運動飛行;具體的操作包括以下幾個步驟:
1.使用UIKit提供的UIBezierPath類創建貝塞爾曲線,作為飛機飛行的路線軌跡;
2.使用CAShapeLayer在屏幕上繪制曲線(此步驟對于動畫不是必須的,只是為了動畫看起來更直觀);
3.創建用于顯示飛機的視圖,將其設置在貝塞爾曲線的初始位置;
4.創建并執行關鍵幀動畫,實現飛機飛行的曲線動畫;
關鍵幀動畫效果圖如下:
CAKeyframeAnimation_path.gif
七、動畫組CAGroupAnimation
CAGroupAnimation顧名思義,就是可以將不同的動畫效果組合起來,CABasicAnimation和CAKeyframeAnimation都僅僅作用于單一的屬性,而CAAnimationGrop可以設置其animations數組的屬性來組合別的動畫,從而達到混合多種動畫效果的目的;
下面演示一個動畫組的示例:組合基礎動畫和關鍵幀動畫,實現一個滑塊在沿path運動過程修改其顏色,具體的測試代碼如下:
@interface TestAnimationGroupVC ()@property (nonatomic,strong) UIView *colorView; @property (nonatomic,strong) UIBezierPath *bezierPath;@end@implementation TestAnimationGroupVC- (void)viewDidLoad {[super viewDidLoad];//創建顯示顏色的圖層self.colorView = [UIView new];self.colorView.frame = CGRectMake(0, 0, 60, 60);self.colorView.center = CGPointMake(50, 200);self.colorView.backgroundColor = [UIColor orangeColor];[self.view addSubview:self.colorView];//創建貝塞爾曲線,即幀動畫運動軌跡self.bezierPath = [[UIBezierPath alloc] init];[self.bezierPath moveToPoint:CGPointMake(50, 200)];[self.bezierPath addCurveToPoint:CGPointMake(kDeviceWidth - 50, 200) controlPoint1:CGPointMake(150, 50) controlPoint2:CGPointMake(kDeviceWidth - 150, 250)];//繪制繪制path,便于觀察動畫;CAShapeLayer *pathLayer = [CAShapeLayer layer];pathLayer.path = self.bezierPath.CGPath;pathLayer.fillColor = [UIColor clearColor].CGColor;pathLayer.strokeColor = [UIColor redColor].CGColor;pathLayer.lineWidth = 3.0f;[self.view.layer addSublayer:pathLayer]; }- (IBAction)startAnimation:(UIButton *)sender{ //移除可能未執行完的動畫,防止多重動畫導致異常[self.colorView.layer removeAnimationForKey:@"groupAnimation"];//1.創建基礎動畫:修改背景色為紫色CABasicAnimation *basicAnimation = [CABasicAnimation animation];basicAnimation.keyPath = @"backgroundColor";basicAnimation.toValue = (__bridge id _Nullable)([UIColor purpleColor].CGColor);//2.創建關鍵幀動畫CAKeyframeAnimation *keyFrameAnimation = [CAKeyframeAnimation animation];keyFrameAnimation.keyPath = @"position";keyFrameAnimation.path = self.bezierPath.CGPath;keyFrameAnimation.rotationMode = kCAAnimationRotateAuto;//3.創建組動畫:組合基礎動畫和關鍵幀動畫CAAnimationGroup *groupAnimation = [CAAnimationGroup animation];groupAnimation.animations = @[basicAnimation, keyFrameAnimation];groupAnimation.duration = 4.0;[self.colorView.layer addAnimation:groupAnimation forKey:@"groupAnimation"]; }動畫組的效果如下:
CAGroupAnimation.gif
八、過渡動畫CATransition
1.過渡動畫簡介
屬性動畫只能對圖層的可動畫屬性起作用,而過渡動畫可以改變非動畫屬性(比如交換一段文本和圖片),或者從層級關系中添加或者移除圖層;于是就有了過渡的概念;
過渡動畫使用CATransition來實現,它同樣是CAAnimation的子類;它并不像屬性動畫那樣在平滑的兩個值之間做動畫,而是影響到整個圖層的變化。過渡動畫首先展示之前的圖層外觀,然后通過一個交換過渡到新的外觀。
過渡動畫通常用于刪除子控件、添加子控件、切換兩個子控件等。
2.過渡動畫屬性介紹
過渡動畫有type和subtype兩個關鍵屬性,type用于指定動畫類型,subtype用于指定動畫移動的方向;
type屬性:
type屬性是一個NSString類型,用于控制整體動畫效果類型,具體的可選類型如下:
| fade | 默認效果,漸變 | kCATransitionFade | 否 |
| moveIn | 覆蓋 | kCATransitionMoveIn | 是 |
| Push | 退出 | kCATransitionPush | 是 |
| Reveal | 揭開 | kCATransitionReveal | 是 |
| cube | 立方體 | 無(私有類型) | 是 |
| suckEffect | 收縮 | 無(私有類型) | 否 |
| oglFlip | 翻轉 | 無(私有類型) | 是 |
| rippleEffect | 水波動畫 | 無(私有類型) | 否 |
| pageCurl | 頁面揭開 | 無(私有類型) | 只支持左右方向 |
| vpageUnCurl | 放下頁面 | 無(私有類型) | 只支持左右方向 |
| cameraIrisHollowOpen | 鏡頭打開 | 無(私有類型) | 否 |
| cameraIrisHollowClose | 鏡頭關閉 | 無(私有類型) | 否 |
目前為止,我們只能使用type的前四種公開屬性,但是我們可以通過一些別的方法來自定義過渡效果(后續介紹);
subtype屬性:
subtype屬性也是一個NSString類型,用于控制動畫方向,具體的可選類型如下:
| kCATransitionFromRight | 從右向左 |
| kCATransitionFromLeft | 從左向右 |
| kCATransitionFromTop | 從上向下 |
| kCATransitionFromBottom | 從下向上 |
3.過渡動畫的使用
現在設想這樣的一個需求:修改UIImageView的image屬性,實現淡入淡出的平滑動畫的效果;此時我們需要使用CATransition來對非動畫屬性做動畫,具體的關鍵代碼如下:
@interface TestTransition1VC ()@property (nonatomic,strong) UIImageView *imageView; @property (nonatomic,strong) NSArray *images;@property (nonatomic, copy) NSString *type; @property (nonatomic, copy) NSString *subtype;@end@implementation TestTransition1VC- (void)viewDidLoad {[super viewDidLoad];self.images = @[[UIImage imageNamed:@"tree_spring"],[UIImage imageNamed:@"tree_summer"],[UIImage imageNamed:@"tree_autumn"],[UIImage imageNamed:@"tree_winter"]];self.type = kCATransitionFade;self.subtype = kCATransitionFromRight; }- (void)perforomTransitionAnimation{CATransition *transition = [[CATransition alloc] init];transition.type = _type;transition.subtype = _subtype;transition.duration = 0.5;[self.imageView.layer addAnimation:transition forKey:nil];UIImage *currentImage = self.imageView.image;NSUInteger index = [self.images indexOfObject:currentImage];index = (index + 1) % self.images.count;self.imageView.image = self.images[index]; }過渡動畫的效果如下:
CATransition.gif
注意:和屬性動畫不同,對指定圖層一次只能使用那一次CATransition,因此無論對動畫的鍵設置為什么值,過渡動畫都會對它的鍵設置為”transition”,也就是常量KCATransition.
4.隱式過渡
CATransition可以對圖層任何變化平滑過渡,這使得它成為那些不好做動畫的屬性圖層行為的理想之選。所以,蘋果將CATransition作為設置CALayer的contents屬性時的默認行為,對圖層contents圖片做的改動都會自動附上淡入淡出的效果,這也就解釋了隱式動畫的原理;
但注意:
1.對于視圖關聯的圖層,過渡動畫的默認效果是禁用的;
2.我們不能錯誤的理解CATransition只可以改變非動畫屬性,其實它也可以對類似backgroundColor的屬性做過渡效果動畫;
5.自定義過渡動畫
過渡動畫的過程就是對原始圖層外觀截圖,然后添加一段動畫,平滑過渡到圖層改變之后的那個截圖效果。如果我們知道如何對圖層截圖,我們就可以使用屬性動畫來自定義CATransition動畫了。
CALayer有一個-renderInContenxt:方法,通過它可以將圖層繪制到Core Graphics的上下文中捕獲當前內容的圖片;所以現在我們嘗試這樣的實現:對當前視圖控制器View進行截圖,然后在改變其背景色的時候對截圖快速旋轉并且淡出,以達到一種過渡的效果;具體的代碼示例如下:
- (void)performAnimation{UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, YES, 0.0);[self.view.layer renderInContext:UIGraphicsGetCurrentContext()];UIImage *coverImage = UIGraphicsGetImageFromCurrentImageContext();UIView *coverView = [[UIImageView alloc] initWithImage:coverImage];coverView.frame = self.view.bounds;[self.view addSubview:coverView];//使用自定義方法得到隨機顏色(切換后的顏色)UIColor *randomColor = [UIColor randomColor];self.view.backgroundColor = randomColor;//使用UIView動畫方法來代替屬性動畫(為了簡化代碼步驟)[UIView animateWithDuration:1 animations:^{CGAffineTransform transform = CGAffineTransformMakeScale(0.01, 0.01);transform = CGAffineTransformRotate(transform, M_PI_2);coverView.transform = transform;coverView.alpha = 0.0;} completion:^(BOOL finished) {[coverView removeFromSuperview];}];}自定義過渡動畫的效果如下:
CATransitionn_Custom.gif
注意:-renderInContext:捕獲了圖層的圖片和子圖層,但是不能對子圖層正確的處理變換效果,而且對視頻和OpenGL內容也不起作用。但是使用CATransition,或者使用私有的截屏方式就沒有這個限制了。
九、委托模式下的動畫區分
對于CAAnimation而言,使用委托模式而不是一個完成塊會帶來一個問題,那就是設置多個動畫時,無法在回調方法中區分。通常視圖控制器本身會作為一個委托,但所有動畫都會調用同一個回調方法,所以我們需要判斷到底是哪個圖層的動畫調用;
首先,動畫本身會作為一個參數傳入委托的方法,也許你會認為可以在控制器中把動畫存儲為一個屬性,然后在回調用比較,但實際上并不起作用,因為委托傳入的動畫參數是原始值的一個深拷貝,從而不是同一個值。最后,這里提供兩種思路來解決這個問題:
思路1:唯一key參數
當使用-addAnimation:forkey:添加動畫到圖層時,對每個動畫都關聯一個唯一的鍵,這樣就可以對每個圖層循環所有鍵,然后調用animationForKey:來對比結果;
思路2:KVC(鍵-值-編碼)協議
像所有NSObject子類一樣,CAAnimation也遵循了KVC協議,就像一個NSDictionary一樣允許我們隨意設置鍵值對;于是我們可以使用setValue:forKey:和-valueForKey:來存取屬性,通過為對象創建一個鍵值對來判斷區分動畫;
驗證上述兩種思路的具體的代碼使用如下:
@interface TestBacicAnimation2VC ()<CAAnimationDelegate>@property (nonatomic,strong) UIView *colorView; @property (nonatomic,strong) UIView *opacityView;@end@implementation TestBacicAnimation2VC#pragma mark - Life Cycle- (void)viewDidLoad {[super viewDidLoad];//創建顯示顏色的圖層UIView *colorView = [UIView new];colorView.frame = CGRectMake(50, 50, 100, 100);colorView.backgroundColor = [UIColor redColor];self.colorView = colorView;[self.view addSubview:self.colorView];//創建透明度視圖UIView *opacityView = [UIView new];opacityView.frame = CGRectMake(50, 200, 100, 100);opacityView.backgroundColor = [UIColor blueColor];self.opacityView = opacityView;[self.view addSubview:self.opacityView]; }- (IBAction)startAnimation:(UIButton *)sender{ //背景色顏色動畫CABasicAnimation *animation1 = [CABasicAnimation animation];animation1.keyPath = @"backgroundColor";animation1.autoreverses = NO;animation1.duration = 1;animation1.repeatCount = 1;animation1.removedOnCompletion = NO;animation1.fillMode = kCAFillModeForwards;animation1.delegate = self;UIColor *randomColor = [UIColor randomColor]; //自定義獲取隨機色的方法animation1.toValue = (__bridge id _Nullable)(randomColor.CGColor);[animation1 setValue:@"animation_background" forKey:@"AnimationKey"];[self.colorView.layer addAnimation:animation1 forKey:@"key_backgroundColor"];//透明度動畫CABasicAnimation *animation2 = [CABasicAnimation animation];animation2.keyPath = @"opacity";animation2.autoreverses = NO;animation2.duration = 5;animation2.repeatCount = 1;animation2.removedOnCompletion = NO;animation2.fillMode = kCAFillModeForwards;animation2.delegate = self;animation2.fromValue = @(1);animation2.toValue = @(0);[animation2 setValue:@"animation_opacity" forKey:@"AnimationKey"];[self.opacityView.layer addAnimation:animation2 forKey:@"key_opacity"]; }//動畫結束的代理:區分動畫- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{//方法1:唯一key參數if([[self.colorView.layer animationForKey:@"key_backgroundColor"] isEqual:anim]){}if([[self.opacityView.layer animationForKey:@"key_opacity"] isEqual:anim]){}//方法2:KVCNSString *animationValue = [anim valueForKey:@"AnimationKey"];NSLog(@"animationValue:%@",animationValue);if([animationValue isEqualToString:@"animation_background"]){}else if([animationValue isEqualToString:@"animation_opacity"]){}}注意:使用唯一key參數這種方法,必須設置removeOnCompletion為NO,否則通過animaitonForKey:獲取的CAAnimation對象為空對象無法進行比較。
十、虛擬屬性
屬性動畫CAPropertyAnimation的keyPath實際上針對的是關鍵路徑而不是一個鍵,這就意味著屬性動畫作用的對象可以子屬性(即屬性的屬性)甚至虛擬屬性;
那么什么是虛擬屬性呢?舉個例子來講,CATransform3D實際上是一個結構體而非一個對象,所以它并不符合KVC相關屬性,但是我們卻可以使用transform.rotation來實現動畫;這其實就是因為transform.rotation是一個CALayer可用于處理動畫變換的虛擬屬性;
1.虛擬屬性的作用
為了理解虛擬屬性的用處,我們現在考慮這樣一個動畫:對一個物體實現旋轉動畫,由于CALayer并沒有顯式的給提供角度或者方向之類的屬性,所以我們自然想到使用transform屬性來實現動畫,測試代碼具體如下:
@interface TestBacicAnimation3VC ()@property(nonatomic, strong) UILabel *txtLabel;@end@implementation TestBacicAnimation3VC- (void)viewDidLoad {[super viewDidLoad];//創建測試虛擬屬性的Label_txtLabel = [UILabel new];_txtLabel.frame = CGRectMake(50, 300, kDeviceWidth -100 , 50);_txtLabel.backgroundColor = [UIColor purpleColor];_txtLabel.font = [UIFont boldSystemFontOfSize:15];_txtLabel.textAlignment = NSTextAlignmentCenter;_txtLabel.text = @"測試虛擬屬性";[self.view addSubview:_txtLabel]; }- (IBAction)startAnimation:(UIButton *)sender{//步驟1:創建動畫CABasicAnimation *animation = [CABasicAnimation animation];animation.keyPath = @"transform”; //代碼1//步驟2:設定動畫屬性animation.autoreverses = NO;animation.removedOnCompletion = NO;animation.fillMode = kCAFillModeForwards;animation.duration = 1;animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(M_PI * 2, 0, 0, 1)]; //代碼2[_txtLabel.layer addAnimation:animation forKey:nil];}在此例中,我們把旋轉角度從M_PI(180度)調整到M_PI*2(360度),對比兩次動畫會發現,txtLabel完全看不到旋轉的動畫效果;這是因為CATransform3D矩陣做了360度旋轉其實適合0度是一樣的,所以最后的值根本就沒變;
這里就需要用到上述說到的虛擬屬性了,為了旋轉圖層,我們可以針對于transform.rotation關鍵路徑應用動畫,而不是transform本身;現在將對上述代碼進行修改如下:
//animation.keyPath = @"transform"; //代碼1 animation.keyPath = @"transform.rotation”;//animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(M_PI * 2, 0, 0, 1)]; //代碼2 animation.byValue = @(M_PI * 2);再來看動畫的效果如下:
CABasicAnimation_VirtualProperty.gif
總結transform.rotation相比transfrom做動畫的好處如下:
2.虛擬屬性原理
我們已經說過CATransform3D是一個結構體而非一個對象,所以transfrom.rotation其實是不存在的,我們不可以直接設置transform.rotation或者transform.scale;
實際上,Core Animation是自動通過CAValueFunction計算的值來更新transform屬性的,CAValueFunction將我們賦值虛擬屬性transfom.rotation的浮點值轉換成了真正能用于擺放圖層的CATransform3D矩陣值;我們也可以通過設置CAPropertyAnimation的valuefunction屬性來改變,這樣我們自定義函數就會覆蓋默認函數。
CAValueFuncation對于那些不能簡單相加的屬性(例如變換矩陣)做動畫十分有用,但是此方法的實現細節是私有的,所以,目前我們并不能通過繼承來自定義此方法;我們可以通過使用蘋果已經提供的常量來改善動畫(目前都是和變換矩陣的虛擬屬性相關,所以沒太多的應用場景了,因為這些屬性都有了默認的實現方式)。
十一、在動畫過程中取消動畫
在使用動畫的過程中,我們可能需要適時的移除不要的動畫,否則就可能造成內存的泄漏問題;從圖層中取消動畫的方法有以下兩種方式:
//方法1:取消指定動畫 /* Remove any animation attached to the layer for 'key'. */ - (void)removeAnimationForKey:(NSString *)key;//方法2:移除所有動畫 /* Remove all animations attached to the layer. */ - (void)removeAllAnimations;關于移除動畫的幾點說明:
1.動畫一旦被移除,圖層的外觀就立刻更新到當前的模型圖層的值;
2.動畫通常默認結束之后被自動移除,除非設置了removeCompletion為NO;
3.動畫若設置為結束之后不自動移除,那么我們在不需要的時候需手動移除,否則它會一直在內存中,直到圖層被銷毀;
測試取消動畫效果圖如下:
CAAnimation_cancel.gif
代碼分析:
-animationDidStop:finished:方法中的flag參數表明了動畫是自然結束還是被打斷的;此例中通過停止按鈕來終止動畫會打印NO,自然完成動畫時打印YES;
總結
以上是生活随笔為你收集整理的iOS动画-CAAnimation使用详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MCR和MRC汇编指令
- 下一篇: 生成对抗网络——GAN(一)