代码地址以下:
http://www.demodashi.com/demo/11603.htmlhtml
关于实现一个iOS动画,若是简单的,咱们能够直接调用UIView
的代码块来实现,虽然使用UIView
封装的方法很方便,可是这只能用于一些简答的动画,若是是一些复杂的动画呢?这就不得不去研究下核心动画Core Animation
(包含在Quartz Core
框架中)了。这这以前咱们必须了解,CALayer
就包含在Quartz Core
框架中,这是一个跨平台的框架,既能够用在iOS中又能够用在Mac OS X中。在使用Core Animation
开发动画的本质就是将CALayer
中的内容转化为位图从而供硬件操做,因此要熟练掌握动画操做必须熟悉CALayer
,关于CALayer
就不在这里讲了。今天主要是分析核心动画,iOS 中的核心动画又分为下面几种:基础动画、关键帧动画、动画组、转场动画、弹簧动画。下面咱们先来了解下各个动画之间的关系spring
@interface CAAnimation : NSObject <NSCoding, NSCopying, CAMediaTiming, CAAction> { @private void *_attr; uint32_t _flags; }
这是核心动画的基类,不能直接使用,主要负责动画的时间、速度等,从上面能够看出是准守CAMediaTiming
协议的。并发
属性动画的基础类,继承自CAAnimation
,不能直接使用。何谓属性动画呢?即经过修改属性值就能够产生动画效果。框架
动画组,继承自CAAnimation
,顾名思义就是一种组合动画,能够经过动画组来进行全部动画行为的统一控制,组中全部动画效果能够并发执行。函数
转场动画,继承自CAAnimation
,主要是经过滤镜来进行动画的效果设置动画
基础动画,继承自CAPropertyAnimation
,经过属性控制动画的参数,只要初始状态和结束状态ui
关键帧动画,继承自CAPropertyAnimation
,也是经过属性控制动画参数,可是与基础动画不一样的是有多个控制状态,而且能够经过path
来实现动画url
弹簧动画,是在iOS 9中引入的,继承自CABasicAnimation
,用于制做弹簧动画3d
在使用动画以前,先补充个知识点---UIBezierPath, 这在动画使用的过程当中会常常用到code
要使用核心动画,咱们必须先了解下其属性,这里咱们先看其遵照的协议<CAMediaTiming>
属性 | 说明 |
---|---|
beginTime | 指定动画开始的时间。开始延迟几秒的话,设置为CACurrentMediaTime() + 秒数 的方式便可 |
duration | 动画的时长 |
speed | 动画的速度 |
timeOffset | 详细说明 |
repeatCount | 动画重复的次数,若是要一直持续设置为HUGE_VALF 便可 |
repeatDuration | 设置动画的时间。在该时间内动画一直执行,不计次数 |
autoreverses | 动画结束时是否执行逆动画 |
fillMode | 分四种状况,分别为kCAFillModeForwards 、kCAFillModeBackwards 、kCAFillModeBoth 、kCAFillModeRemoved ,决定当前对象在非active时间段的行为,好比动画开始以前或者动画结束之 |
CAAnimation
属性
属性 | 说明 |
---|---|
timingFunction | 速度控制函数,控制动画运行的节奏 |
removedOnCompletion | 默认为YES ,表明动画执行完毕后就从图层上移除,图形会恢复到动画执行前的状态。若是想让图层保持显示动画执行后的状态,那就设置为NO ,不过还要设置fillMode 为kCAFillModeForwards |
关于fillMode
的四种状况:
kCAFillModeRemoved
默认值,动画结束后,layer会恢复到以前的状态
kCAFillModeForwards
当动画结束后,layer会一直保持着动画最后的状态,而removedOnCompletion
的默认属性值是 YES
,因此为了使动画结束以后layer保持结束状态,应将removedOnCompletion
设置为NO
。
kCAFillModeBackwards
在动画开始前,只须要将动画加入了一个layer,layer便当即进入动画的初始状态并等待动画开始。
kCAFillModeBoth
这个其实就是上面两个的合成,动画加入后开始以前,layer便处于动画初始状态,动画结束后layer保持动画最后的状态
关于速度CAMediaTimingFunction
控制的四种状况
kCAMediaTimingFunctionLinear
(线性):匀速,给你一个相对静态的感受
kCAMediaTimingFunctionEaseIn
(渐进):动画缓慢进入,而后加速离开
kCAMediaTimingFunctionEaseOut
(渐出):动画全速进入,而后减速的到达目的地
kCAMediaTimingFunctionEaseInEaseOut
(渐进渐出):动画缓慢的进入,中间加速,而后减速的到达目的地。这个是默认的动画行为。
从上图中咱们知道,属性动画是继承自核心动画,在其API中咱们能够看到有以下函数和属性
+ (instancetype)animationWithKeyPath:(nullable NSString *)path; @property(nullable, copy) NSString *keyPath;
其中都有keyPath
,这又是什么呢?这就是属性动画与动画组和转场动画不一样之处。经过指定CALayer
的一个属性名称为keyPath
(NSString类型),而且对CALayer
的这个属性的值进行修改,达到相应的动画效果。好比,指定@"opacity"
为keyPath
,就修改CALayer
的opacity
属性的值,以达到透明度变化的动画效果
下面咱们列举一些经常使用的animationWithKeyPath
值
经常使用值 | 说明 | 使用方式 |
---|---|---|
transform | 3D变换 | [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.5, 0.5, 0)] ,直接进行3D变换 |
transform.scale | 缩放 | @(1.5) ,放大1.5倍 |
transform.scale.x | 宽度缩放 | @(1.5) ,宽放大1.5倍 |
transform.scale.y | 高度缩放 | @(1.5) ,高放大1.5倍 |
transform.rotation.x | 围绕x轴旋转 | @(M_PI) ,x轴旋转180度 |
transform.rotation.y | 围绕y轴旋转 | @(M_PI) ,y轴旋转180度 |
transform.rotation.z | 围绕z轴旋转 | @(M_PI) ,z轴旋转180度 |
position | 位置(中心点的改变) | [NSValue valueWithCGPoint:CGPointMake(100, 100)] ,中心点变为(100,100) |
opacity | 透明度 | @(0.5) ,透明度变为0.5 |
bounds | 大小的改变 中心点保持不变 | [NSValue valueWithCGRect:CGRectMake(0, 0, 300, 300)] ,大小变为(300,300) |
cornerRadius | 圆角的设置 | @(5) ,圆角设置为5 |
backgroundColor | 背景颜色变换 | (id)[UIColor redColor].CGColor ,背景改成红色 |
contents | 能够改变layer展现的图片 | (id)[UIImage imageNamed:@"12.png"].CGImage ,将UIView的展现图片改成12.png |
strokeStart | 从起始点开始变化 | fromValue = 0 ,toValue = 1 ,为CAShapeLayer 的属性 |
strokeEnd | 从结束的位置开始变化 | fromValue = 1 ,toValue = 0.5 ,为CAShapeLayer 的属性 |
path | 根据路径来改变 | fromValue = (__bridge id)(start.CGPath); ,toValue = (__bridge id)((end.CGPath)) |
基础动画是继承自属性动画,因此在使用的时候,咱们最主要的就是经过属性来控制动画,好比设置初始值、结束值,固然还有核心动画的其余属性。
- (void)showAnimation { CAShapeLayer * circle = [CAShapeLayer layer]; circle.frame = self.view.bounds; // UIBezierPath * circlePath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds)) radius:self.normalSize.width/2 -1 startAngle:0 endAngle:2*M_PI clockwise:YES]; circle.path = circlePath.CGPath; circle.strokeColor = [UIColor blueColor].CGColor; circle.fillColor = nil; [self.view.layer addSublayer:circle]; //经过圆的strokeStart 改变来进行改变 CABasicAnimation * strokeStartAnimation = [CABasicAnimation animationWithKeyPath:@"strokeStart"]; strokeStartAnimation.fromValue = @(0.2); strokeStartAnimation.toValue = @(0); //经过圆的strokeEnd 改变来进行改变 CABasicAnimation * strokeEndAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"]; strokeEndAnimation.fromValue = @0.5; strokeEndAnimation.toValue = @(1.0); //经过圆的transform.rotation.z 改变来进行改变 CABasicAnimation * rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"]; rotationAnimation.fromValue = @(0); rotationAnimation.toValue = @(-M_PI * 2); //组合动画 CAAnimationGroup * group = [CAAnimationGroup animation]; group.duration = 5; group.removedOnCompletion = NO; group.fillMode = kCAFillModeForwards; group.animations = @[strokeStartAnimation,strokeEndAnimation,rotationAnimation]; [circle addAnimation:group forKey:nil]; }
在上面的基础动画代码中,我只用了最基础的两个属性,fromValue
和toValue
,而其它属性呢?因为后面用到了动画组,因此讲其它属性在动画组进行了设置。[circle addAnimation:group forKey:nil];
这句代码中,key
我设置的为nil
,若是不设置为nil
的时候,是什么意思呢?这个其实就是咱们的动画设置了一个key
,能够用来区别是哪个动画,在后面我会举例说明。
上面代码对应的效果以下:
关键帧动画也是属性动画,与基础动画最主要不一样的是在两个参数上,NSArray *values
和CGPathRef path
,经过这个咱们能够知晓,关键帧动画能够设置多个控制状态
以下:
//用value的方式进行展现动画 - (void)showKeyFrameAnimationWithValues { CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"]; NSValue *key1 = [NSValue valueWithCGPoint:_beginPoint]; NSValue *key2 = [NSValue valueWithCGPoint:CGPointMake(100, 100)]; NSValue *key3 = [NSValue valueWithCGPoint:CGPointMake(150, 50)]; NSValue *key4 = [NSValue valueWithCGPoint:CGPointMake(300, 250)]; animation.values = @[key1,key2,key3,key4]; animation.duration = 5.0; animation.delegate = (id)self; // animation.autoreverses = true;//是否按路径返回 // animation.repeatCount = HUGE;//是否重复执行 animation.removedOnCompletion = NO;//执行后移除动画 animation.fillMode = kCAFillModeForwards; //存储位置 [animation setValue:key4 forKey:@"keyframeAnimationLocation"]; [_fishImageView.layer addAnimation:animation forKey:@"keyframeAnimation_fish"]; }
并且还能够设置路径,这也是其最大的特色
以下:
//用CGPathRef的方式进行展现动画 - (void)showKeyFrameAnimationWithPath { CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"]; UIBezierPath *path = [UIBezierPath bezierPath]; [path moveToPoint:_fishImageView.layer.position]; //三次贝塞尔曲线 [path addCurveToPoint:CGPointMake(300, 200) controlPoint1:CGPointMake(150, 400) controlPoint2:CGPointMake(230, -100)]; animation.path = path.CGPath; animation.duration = 5.0; animation.delegate = (id)self; [_fishImageView.layer addAnimation:animation forKey:@"keyframeAnimation_path_fish"]; }
效果以下,这里就暂时只给出values
的效果
在上面的两个方法中,对layer
设置了两个不一样的key
,分别为keyframeAnimation_fish
和keyframeAnimation_path_fish
前面我提到过,经过这个能够判断是哪种动画,这里咱们在动画结束的地方进行区分一下
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag { if ([anim isEqual:[_fishImageView.layer animationForKey:@"keyframeAnimation_fish"]]) { // [CATransaction begin]; // //禁用隐式动画 // [CATransaction setDisableActions:YES]; _fishImageView.layer.position = [[anim valueForKey:@"keyframeAnimationLocation"] CGPointValue]; // //提交事务 // [CATransaction commit]; } else if ([anim isEqual:[_fishImageView.layer animationForKey:@"keyframeAnimation_path_fish"]]) { } }
动画组其实很简单,就是将许多动画组合在一块儿
在上面的基础动画中,我也用到了动画组,下面再贴上一组动画组合效果
//发射 - (void)launchAnimation { CABasicAnimation *animation1 = [CABasicAnimation animationWithKeyPath:@"transform.scale.y"]; animation1.fromValue = @(1.0); animation1.toValue = @(1.5); CABasicAnimation *animation2 = [CABasicAnimation animationWithKeyPath:@"transform.scale.x"]; animation2.fromValue = @(1.0); animation2.toValue = @(1.5); CABasicAnimation *animation3 = [CABasicAnimation animationWithKeyPath:@"position"]; animation3.fromValue = [NSValue valueWithCGPoint:_ballLayer.position]; animation3.toValue = [NSValue valueWithCGPoint:CGPointMake(self.view.frame.size.width - (30 * 1.3)/2.0 , _ballLayer.position.y - 200)]; CAAnimationGroup *anima = [CAAnimationGroup animation]; anima.animations = @[animation1, animation2,animation3]; anima.duration = 1.0; anima.delegate = (id)self; anima.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; anima.fillMode = kCAFillModeForwards; anima.removedOnCompletion = NO; [_ballLayer addAnimation:anima forKey:@"group_launch"]; }
在后面的demo里有完整的代码,这里只是截取了部分代码
效果以下:
在了解转场动画以前,咱们先了解其两个参数,经过这些参数,咱们就能很清楚的了解其效果
type
转场类型转场动画类型 | 说明 | 常量 | 是否支持方向设置 |
---|---|---|---|
公开API | |||
fade | 淡出 | kCATransitionFade |
是 |
movein | 新视图移动到旧视图上面 | kCATransitionMoveIn |
是 |
push | 新视图退出旧视图 | kCATransitionPush |
是 |
reveal | 移开旧视图显示新的 | kCATransitionReveal |
是 |
私有API | 苹果未公开的类型,可是目前仍是能够用的 | 私有API只能经过下面的字符串进行访问 | |
cube | 立体翻转 | 是 | |
oglFlip | 翻转 | 是 | |
suckEffect | 收缩 | 否 | |
rippleEffect | 水滴波纹效果 | 否 | |
pageCurl | 向上翻页效果 | 是 | |
pageUnCurl | 向下翻页效果 | 是 | |
cameraIrisHollowOpen | 摄像头打开效果 | 否 | |
cameraIrisHollowClose | 摄像头关闭效果 | 否 |
subtype
动画子类型属性 | 说明 |
---|---|
kCATransitionFromRight | 从右 |
kCATransitionFromLeft | 从左 |
kCATransitionFromTop | 从顶部 |
kCATransitionFromBottom | 从底部 |
在了解上面两个属性后,对应转场动画,就差很少了
部分代码以下
- (void)transition:(BOOL)next { CATransition *transition = [CATransition animation]; transition.type = @"cube"; if (next) { transition.subtype = kCATransitionFromLeft; } else { transition.subtype = kCATransitionFromRight; } transition.duration = 1.0; _imageView.image = [UIImage imageNamed:[NSString stringWithFormat:@"%ld.jpg",(long)_currentIndex]]; [_imageView.layer addAnimation:transition forKey:@"transitionAnimation"]; }
效果以下
弹簧动画是在iOS 9后才出现的,在这以前,咱们能够经过下面的方法来实现
[UIView animateWithDuration:5.0 delay:0 usingSpringWithDamping:0.1 initialSpringVelocity:1.0 options:UIViewAnimationOptionCurveLinear animations:^{ } completion:nil];
iOS 9后苹果公开了这一API,咱们先对其中的属性进行分析,由于代码中有注释,因此就直接贴一部分代码
//质量,影响图层运动时的弹簧惯性,质量越大,弹簧拉伸和压缩的幅度越大 positionAnimation.mass = 0.1; //阻尼系数,阻止弹簧伸缩的系数,阻尼系数越大,中止越快 positionAnimation.damping = 2; //刚度系数(劲度系数/弹性系数),刚度系数越大,形变产生的力就越大,运动越快 positionAnimation.stiffness = 50; //初始速率,动画视图的初始速度大小 //速率为正数时,速度方向与运动方向一致,速率为负数时,速度方向与运动方向相反 positionAnimation.initialVelocity = -10;
关于弹簧动画,我也写了一个例子,效果以下
关于核心动画,差很少就简单的介绍这么点,若有什么不对的还望各位多多指教,不甚感激。iOS 核心动画 Core Animation浅谈
代码地址以下:
http://www.demodashi.com/demo/11603.html
注:本文著做权归做者,由demo大师代发,拒绝转载,转载须要做者受权