【原】iOSCoreAnimation動畫系列教程(二):CAKeyFrameAnimation【包會】


  在上一篇專題文章【原】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
ThrowLineTool.h
 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
ThrowLineTool.m
 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/機器視覺等領域展開交流!

 =======================================================


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM