在上一篇專題文章【原】iOSCoreAnimation動畫系列教程(一):CABasicAnimation【包會】中我們學習了iOS核心動畫CoreAnimation中CABasicAnimation動畫的使用方法。CABasicAnimation已經可以應付一些比較簡單的應用場景了,比如view的平移出現、淡入淡出等。但是在有些情況下直線的運動並不能滿足我們的需要,因此有必要學習進階版的核心動畫,那就是CAKeyFrameAnimation。
在上一篇專題中我們提到,CAAnimation可分為以下四種:
1.CABasicAnimation 通過設定起始點,終點,時間,動畫會沿着你這設定點進行移動。可以看做特殊的CAKeyFrameAnimation 2.CAKeyframeAnimation Keyframe顧名思義就是關鍵點的frame,你可以通過設定CALayer的始點、中間關鍵點、終點的frame,時間,動畫會沿你設定的軌跡進行移動 3.CAAnimationGroup Group也就是組合的意思,就是把對這個Layer的所有動畫都組合起來。PS:一個layer設定了很多動畫,他們都會同時執行,如何按順序執行我到時候再講。 4.CATransition 這個就是蘋果幫開發者封裝好的一些動畫,
CABasicAnimation算是CAKeyFrameAnimation的特殊情況,即不考慮中間變換過程,只考慮起始點與目標點就可以了。而CAKeyFrameAnimation則更復雜一些,允許我們在起點與終點間自定義更多內容來達到我們的實際應用需求!比如,手機淘寶中,當你添加物品到購物車后會出現將物品拋到購物車的效果,這種效果實現起來也不難,無非是先繪制拋物線在執行position以及scale的GroupAnimation而已,以下圖1是我模仿該功能小玩出來的一個demo示例,感興趣的話你可以自己實現一下試試:D.
圖1 圖2
下面我們以實現“小圓球繞矩形跑道循環跑動”為目標開始對CAKeyFrameAnimation的介紹,如圖2所示。小圓球的運動軌跡可分為四段,每段的運動速度不同,第一段中先慢后快再慢。先貼上源碼方便后面分析:
1 //繞矩形循環跑 2 - (void)initRectLayer 3 { 4 rectLayer = [[CALayer alloc] init]; 5 rectLayer.frame = CGRectMake(15, 200, 30, 30); 6 rectLayer.cornerRadius = 15; 7 rectLayer.backgroundColor = [[UIColor blackColor] CGColor]; 8 [self.view.layer addSublayer:rectLayer]; 9 CAKeyframeAnimation *rectRunAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"]; 10 //設定關鍵幀位置,必須含起始與終止位置 11 rectRunAnimation.values = @[[NSValue valueWithCGPoint:rectLayer.frame.origin], 12 [NSValue valueWithCGPoint:CGPointMake(320 - 15, 13 rectLayer.frame.origin.y)], 14 [NSValue valueWithCGPoint:CGPointMake(320 - 15, 15 rectLayer.frame.origin.y + 100)], 16 [NSValue valueWithCGPoint:CGPointMake(15, rectLayer.frame.origin.y + 100)], 17 [NSValue valueWithCGPoint:rectLayer.frame.origin]]; 18 //設定每個關鍵幀的時長,如果沒有顯式地設置,則默認每個幀的時間=總duration/(values.count - 1) 19 rectRunAnimation.keyTimes = @[[NSNumber numberWithFloat:0.0], [NSNumber numberWithFloat:0.6], 20 [NSNumber numberWithFloat:0.7], [NSNumber numberWithFloat:0.8], 21 [NSNumber numberWithFloat:1]]; 22 rectRunAnimation.timingFunctions = @[[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut], 23 [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear], 24 [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear], 25 [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]]; 26 rectRunAnimation.repeatCount = 1000; 27 rectRunAnimation.autoreverses = NO; 28 rectRunAnimation.calculationMode = kCAAnimationLinear; 29 rectRunAnimation.duration = 4; 30 [rectLayer addAnimation:rectRunAnimation forKey:@"rectRunAnimation"]; 31 }
圖3
對CAKeyFrameAnimation的使用與CABasicAnimation大同小異,有些屬性是共通的,因此小翁建議你先閱讀上一篇文章。KeyFrame的意思是關鍵幀,所謂“關鍵”就是改變物體運動趨勢的幀,在該點處物體將發生運動狀態,比如矩形的四個角,拋物線的頂點等。因此,聰明的你應該知道了,在上述例子中共有5個關鍵幀(圖3中的ABCDE)。上個關鍵幀到當前關鍵幀之間的路徑與當前關鍵幀相聯系,比如AB->B,我們可以對AB進行定義動畫定義,而自定義要通過眾多CAKeyFrameAnimation的屬性達到目的。CAKeyFrameAnimation的使用中有以下主要的屬性需要注意,有些屬性可能比較繞比較難以理解,我會結合圖片進行必要的說明。
(1)values屬性
values屬性指明整個動畫過程中的關鍵幀點,例如上例中的A-E就是通過values指定的。需要注意的是,起點必須作為values的第一個值。
(2)path屬性
作用與values屬性一樣,同樣是用於指定整個動畫所經過的路徑的。需要注意的是,values與path是互斥的,當values與path同時指定時,path會覆蓋values,即values屬性將被忽略。例如上述例子等價於代碼中values方式的path設置方式為:
1 CGMutablePathRef path = CGPathCreateMutable(); 2 CGPathMoveToPoint(path, NULL, rectLayer.position.x - 15, rectLayer.position.y - 15); 3 CGPathAddLineToPoint(path, NULL, 320 - 15, rectLayer.frame.origin.y); 4 CGPathAddLineToPoint(path, NULL, 320 - 15, rectLayer.frame.origin.y + 100); 5 CGPathAddLineToPoint(path, NULL, 15, rectLayer.frame.origin.y + 100); 6 CGPathAddLineToPoint(path, NULL, 15, rectLayer.frame.origin.y); 7 rectRunAnimation.path = path; 8 CGPathRelease(path);
(3)keyTimes屬性
該屬性是一個數組,用以指定每個子路徑(AB,BC,CD)的時間。如果你沒有顯式地對keyTimes進行設置,則系統會默認每條子路徑的時間為:ti=duration/(5-1),即每條子路徑的duration相等,都為duration的1\4。當然,我們也可以傳個數組讓物體快慢結合。例如,你可以傳入{0.0, 0.1,0.6,0.7,1.0},其中首尾必須分別是0和1,因此tAB=0.1-0, tCB=0.6-0.1, tDC=0.7-0.6, tED=1-0.7.....
(4)timeFunctions屬性
用過UIKit層動畫的同學應該對這個屬性不陌生,這個屬性用以指定時間函數,類似於運動的加速度,有以下幾種類型。上例子的AB段就是用了淡入淡出效果。記住,這是一個數組,你有幾個子路徑就應該傳入幾個元素
1 kCAMediaTimingFunctionLinear//線性 2 kCAMediaTimingFunctionEaseIn//淡入 3 kCAMediaTimingFunctionEaseOut//淡出 4 kCAMediaTimingFunctionEaseInEaseOut//淡入淡出 5 kCAMediaTimingFunctionDefault//默認
(5)calculationMode屬性
該屬性決定了物體在每個子路徑下是跳着走還是勻速走,跟timeFunctions屬性有點類似
1 const kCAAnimationLinear//線性,默認 2 const kCAAnimationDiscrete//離散,無中間過程,但keyTimes設置的時間依舊生效,物體跳躍地出現在各個關鍵幀上 3 const kCAAnimationPaced//平均,keyTimes跟timeFunctions失效 4 const kCAAnimationCubic//平均,同上 5 const kCAAnimationCubicPaced//平均,同上
此外,動畫的暫停與開始可以通過下面的方式做到:
1 -(void)pauseLayer:(CALayer*)layer 2 { 3 CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil]; 4 layer.speed = 0.0; 5 layer.timeOffset = pausedTime; 6 } 7 8 -(void)resumeLayer:(CALayer*)layer 9 { 10 CFTimeInterval pausedTime = [layer timeOffset]; 11 layer.speed = 1.0; 12 layer.timeOffset = 0.0; 13 layer.beginTime = 0.0; 14 CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime; 15 layer.beginTime = timeSincePause; 16 }
更多更詳細的關於這些屬性的介紹可以進一步閱讀此文。
關於CAKeyFrameAnimation的介紹基本結束了,在文章的最后,開源一個小翁封裝的拋物動畫代碼,上文的圖1就是在這份代碼的基礎上實現的:

1 #import <Foundation/Foundation.h> 2 #import <UIKit/UIKit.h> 3 #import <QuartzCore/QuartzCore.h> 4 5 @protocol ThrowLineToolDelegate; 6 7 @interface ThrowLineTool : NSObject 8 9 @property (nonatomic, assign) id<ThrowLineToolDelegate>delegate; 10 @property (nonatomic, retain) UIView *showingView; 11 12 + (ThrowLineTool *)sharedTool; 13 14 /** 15 * 將某個view或者layer從起點拋到終點 16 * 17 * @param obj 被拋的物體 18 * @param start 起點坐標 19 * @param end 終點坐標 20 * @param height 高度,拋物線最高點比起點/終點y坐標最低(即高度最高)所超出的高度 21 */ 22 - (void)throwObject:(UIView *)obj from:(CGPoint)start to:(CGPoint)end 23 height:(CGFloat)height duration:(CGFloat)duration; 24 25 @end 26 27 28 @protocol ThrowLineToolDelegate <NSObject> 29 30 /** 31 * 拋物線結束的回調 32 */ 33 - (void)animationDidFinish; 34 35 @end

1 #import "ThrowLineTool.h" 2 3 static ThrowLineTool *s_sharedInstance = nil; 4 @implementation ThrowLineTool 5 6 + (ThrowLineTool *)sharedTool 7 { 8 if (!s_sharedInstance) { 9 s_sharedInstance = [[[self class] alloc] init]; 10 } 11 return s_sharedInstance; 12 } 13 14 /** 15 * 將某個view或者layer從起點拋到終點 16 * 17 * @param obj 被拋的物體 18 * @param start 起點坐標 19 * @param end 終點坐標 20 * @param height 高度,拋物線最高點比起點/終點y坐標最低(即高度最高)所超出的高度 21 */ 22 - (void)throwObject:(UIView *)obj from:(CGPoint)start to:(CGPoint)end 23 height:(CGFloat)height duration:(CGFloat)duration 24 { 25 self.showingView = obj; 26 //初始化拋物線path 27 CGMutablePathRef path = CGPathCreateMutable(); 28 CGFloat cpx = (start.x + end.x) / 2; 29 CGFloat cpy = -height; 30 CGPathMoveToPoint(path, NULL, start.x, start.y); 31 CGPathAddQuadCurveToPoint(path, NULL, cpx, cpy, end.x, end.y); 32 CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"]; 33 animation.path = path; 34 CFRelease(path); 35 CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"]; 36 scaleAnimation.autoreverses = YES; 37 scaleAnimation.toValue = [NSNumber numberWithFloat:(CGFloat)((arc4random() % 4) + 4) / 10.0]; 38 39 CAAnimationGroup *groupAnimation = [CAAnimationGroup animation]; 40 groupAnimation.delegate = self; 41 groupAnimation.repeatCount = 1; 42 groupAnimation.duration = duration; 43 groupAnimation.removedOnCompletion = NO; 44 groupAnimation.animations = @[scaleAnimation, animation]; 45 [obj.layer addAnimation:groupAnimation forKey:@"position scale"]; 46 } 47 48 - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag 49 { 50 if (self.delegate && [self.delegate respondsToSelector:@selector(animationDidFinish)]) { 51 [self.delegate performSelector:@selector(animationDidFinish) withObject:nil]; 52 } 53 self.showingView = nil; 54 } 55 56 - (void)dealloc 57 { 58 [super dealloc]; 59 } 60 61 @end

1 - (void)beginThrowing:(UIView *)view 2 { 3 ThrowLineTool *tool = [ThrowLineTool sharedTool]; 4 tool.delegate = self; 5 UIImageView *bagImgView = (UIImageView *)[self viewWithTag:1000]; 6 CGFloat startX = 0;//arc4random() % (NSInteger)CGRectGetWidth(self.frame); 7 CGFloat startY = 150;//CGRectGetHeight(self.frame); 8 CGFloat endX = CGRectGetMidX(bagImgView.frame) + 10 - (arc4random() % 50); 9 CGFloat endY = CGRectGetMidY(bagImgView.frame); 10 CGFloat height = 50 + arc4random() % 40; 11 [tool throwObject:view 12 from:CGPointMake(startX, startY) 13 to:CGPointMake(endX, endY) 14 height:height duration:1.6]; 15 }
=======================================================
原創文章,轉載請注明 編程小翁@博客園,郵件zilin_weng@163.com,歡迎各位與我在C/C++/Objective-C/機器視覺等領域展開交流!
=======================================================