【原】iOSCoreAnimation动画系列教程(二):CAKeyFrameAnimation【包会】
在上一篇专题文章【原】iOSCoreAnimation动画系列教程(一):CABasicAnimation【包会】中我们学习了iOS核心动画CoreAnimation中CABasicAnimation动画的使用方法。CABasicAnimation已经可以应付一些比较简单的应用场景了,比如view的平移出现、淡入淡出等。但是在有些情况下直线的运动并不能满足我们的需要,因此有必要学习进阶版的核心动画,那就是CAKeyFrameAnimation。
在上一篇专题中我们提到,CAAnimation可分为以下四种:
1 2 3 4 5 6 7 8 |
|
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 }