iOS动画篇:核心动画
KatJudkins
9年前
<h3>基本概念</h3> <p>1、什么是核心动画</p> <p> Core Animation(核心动画)是一组功能强大、效果华丽的动画API,无论在iOS系统或者在你开发的App中,都有大量应用。<br> 核心动画所在的位置如下图所示:</p> <p><img src="https://simg.open-open.com/show/b593918ccd20434a2e4ec67fd14c1f54.png" alt="iOS动画篇:核心动画" width="300" height="222"></p> <p>Core Animation.png</p> <p><br> 可以看到,核心动画位于UIKit的下一层,相比UIView动画,它可以实现更复杂的动画效果。</p> <p> 核心动画作用在CALayer(Core animation layer)上,CALayer从概念上类似UIView,我们可以将UIView看成是一种特殊的CALayer(可以响应事件)。<br> 实际上,每一个view都有其对应的layer,这个layer是root layer:</p> <pre> <code class="language-objectivec"> @property(nonatomic,readonly,strong) CALayer *layer;</code></pre> <p> 给view加上动画,本质上是对其layer进行操作,layer包含了各种支持动画的属性,动画则包含了属性变化的值、变化的速度、变化的时间等等,两者结合产生动画的过程。</p> <p> 核心动画和UIView动画的对比:UIView动画可以看成是对核心动画的封装,和UIView动画不同的是,通过核心动画改变layer的状态(比如position),动画执行完毕后实际上是没有改变的(表面上看起来已改变)。</p> <p> 总体来说核心动画的优点有:</p> <p> 1)性能强大,使用硬件加速,可以同时向多个图层添加不同的动画效果<br> 2)接口易用,只需要少量的代码就可以实现复杂的动画效果。<br> 3)运行在后台线程中,在动画过程中可以响应交互事件(UIView动画默认动画过程中不响应交互事件)。</p> <p>2、核心动画类的层次结构</p> <p><img src="https://simg.open-open.com/show/054fe81fa7828e37658a8ced8bf639dd.png" alt="iOS动画篇:核心动画" width="428" height="458"></p> <p>Core Animation classes and protocol.png</p> <p><br> CAAnimation是所有动画对象的父类,实现CAMediaTiming协议,负责控制动画的时间、速度和时间曲线等等,是一个抽象类,不能直接使用。<br> CAPropertyAnimation :是CAAnimation的子类,它支持动画地显示图层的keyPath,一般不直接使用。<br> iOS9.0之后新增CASpringAnimation类,它实现弹簧效果的动画,是CABasicAnimation的子类。</p> <p> 综上,核心动画类中可以直接使用的类有:</p> <p> CABasicAnimation<br> CAKeyframeAnimation<br> CATransition<br> CAAnimationGroup<br> CASpringAnimation</p> <p>3、核心动画类的核心方法</p> <p> 1.初始化CAAnimation对象<br> 一般使用animation方法生成实例</p> <pre> <code class="language-objectivec"> + (instancetype)animation;</code></pre> <p> 如果是CAPropertyAnimation的子类,还可以通过animationWithKeyPath生成实例</p> <pre> <code class="language-objectivec"> + (instancetype)animationWithKeyPath:(nullable NSString *)path;</code></pre> <p> 2.设置动画的相关属性<br> 设置动画的执行时间,执行曲线,keyPath的目标值,代理等等</p> <p> 3.动画的添加和移除<br> 调用CALayer的addAnimation:forKey:方法将动画添加到CALayer中,这样动画就开始执行了</p> <pre> <code class="language-objectivec"> - (void)addAnimation:(CAAnimation *)anim forKey:(nullable NSString *)key;</code></pre> <p> 调用CALayer的removeAnimation方法停止CALayer中的动画</p> <pre> <code class="language-objectivec"> - (void)removeAnimationForKey:(NSString *)key; - (void)removeAllAnimations;</code></pre> <p>4、核心动画类的常用属性</p> <p> keyPath:可以指定keyPath为CALayer的属性值,并对它的值进行修改,以达到对应的动画效果,需要注意的是部分属性值是不支持动画效果的。<br> 以下是具有动画效果的keyPath:</p> <pre> <code class="language-objectivec"> //CATransform3D Key Paths : (example)transform.rotation.z //rotation.x //rotation.y //rotation.z //rotation 旋轉 //scale.x //scale.y //scale.z //scale 缩放 //translation.x //translation.y //translation.z //translation 平移 //CGPoint Key Paths : (example)position.x //x //y //CGRect Key Paths : (example)bounds.size.width //origin.x //origin.y //origin //size.width //size.height //size //opacity //backgroundColor //cornerRadius //borderWidth //contents //Shadow Key Path: //shadowColor //shadowOffset //shadowOpacity //shadowRadius</code></pre> <p> duration:动画的持续时间<br> repeatCount:动画的重复次数<br> timingFunction:动画的时间节奏控制</p> <pre> <code class="language-objectivec"> timingFunctionName的enum值如下: kCAMediaTimingFunctionLinear 匀速 kCAMediaTimingFunctionEaseIn 慢进 kCAMediaTimingFunctionEaseOut 慢出 kCAMediaTimingFunctionEaseInEaseOut 慢进慢出 kCAMediaTimingFunctionDefault 默认值(慢进慢出)</code></pre> <p> fillMode:视图在非Active时的行为<br> removedOnCompletion:动画执行完毕后是否从图层上移除,默认为YES(视图会恢复到动画前的状态),可设置为NO(图层保持动画执行后的状态,前提是fillMode设置为kCAFillModeForwards)<br> beginTime:动画延迟执行时间(通过CACurrentMediaTime() + your time 设置)<br> delegate:代理</p> <pre> <code class="language-objectivec"> 代理方法如下: - (void)animationDidStart:(CAAnimation *)anim; //动画开始 - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag; //动画结束</code></pre> <h3>CABasicAnimation</h3> <p> CABasicAnimation可以设定keyPath的起点,终点的值,动画会沿着设定点进行移动,CABasicAnimation可以看成是只有两个关键点的特殊的CAKeyFrameAnimation。</p> <p>下面以改变视图的position为例演示其使用:</p> <pre> <code class="language-objectivec">- (void)position { CABasicAnimation * ani = [CABasicAnimation animationWithKeyPath:@"position"]; ani.toValue = [NSValue valueWithCGPoint:self.centerShow.center]; ani.removedOnCompletion = NO; ani.fillMode = kCAFillModeForwards; ani.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; [self.cartCenter.layer addAnimation:ani forKey:@"PostionAni"]; }</code></pre> <p> 动画效果:</p> <p><img src="https://simg.open-open.com/show/b34d4bde517502f048bb2f0d4e0430e1.gif" alt="iOS动画篇:核心动画" width="372" height="292"></p> <p>position.gif</p> <p> 下面是部分keyPath对应的动画效果(图片名为其对应的keyPath):</p> <p><img src="https://simg.open-open.com/show/abd2fbd0a42749818f354112ab79ec5d.gif" alt="iOS动画篇:核心动画" width="372" height="292"></p> <p>positionX.gif</p> <p><img src="https://simg.open-open.com/show/a74153771ab8752011c0edd421bef632.gif" alt="iOS动画篇:核心动画" width="372" height="292"></p> <p>positionY.gif</p> <p><img src="https://simg.open-open.com/show/4d9e5556efad4163c7ae489ee54b9f98.gif" alt="iOS动画篇:核心动画" width="372" height="292"></p> <p>rotation.gif</p> <p><img src="https://simg.open-open.com/show/8d270959c06871d581250221968878ea.gif" alt="iOS动画篇:核心动画" width="372" height="292"></p> <p>rotationX.gif</p> <p><img src="https://simg.open-open.com/show/fbd41aa58a7102ca201754fa66ef262d.gif" alt="iOS动画篇:核心动画" width="372" height="292"></p> <p>rotationY.gif</p> <p><img src="https://simg.open-open.com/show/aa2e0e8a34df533ecfb8793458a9aabf.gif" alt="iOS动画篇:核心动画" width="372" height="292"></p> <p>rotationZ.gif</p> <p><img src="https://simg.open-open.com/show/ca4d2f0f26668b5604a1fa97ac95a2a4.gif" alt="iOS动画篇:核心动画" width="372" height="292"></p> <p>scale.gif</p> <p><img src="https://simg.open-open.com/show/fc5e01adf6cc2bb47f3df5fe1485cfdb.gif" alt="iOS动画篇:核心动画" width="372" height="292"></p> <p>translation.gif</p> <p><img src="https://simg.open-open.com/show/7d2b37914d2f2258fb6207a305ab1e3e.gif" alt="iOS动画篇:核心动画" width="372" height="292"></p> <p>transform的组合效果.gif</p> <p><img src="https://simg.open-open.com/show/1bdb3fa873758c53bff3fbcbccb7bac2.gif" alt="iOS动画篇:核心动画" width="372" height="292"></p> <p>bounds.gif</p> <p><img src="https://simg.open-open.com/show/ffcb4b1de2af6859ce89b9236868b999.gif" alt="iOS动画篇:核心动画" width="372" height="292"></p> <p>size.gif</p> <p><img src="https://simg.open-open.com/show/831753b02f6fef4e3e37e9325394a25c.gif" alt="iOS动画篇:核心动画" width="372" height="292"></p> <p>sizeW.gif</p> <p><img src="https://simg.open-open.com/show/a82bfccc8566d3322f34460e6a182740.gif" alt="iOS动画篇:核心动画" width="372" height="292"></p> <p>sizeH.gif</p> <p><img src="https://simg.open-open.com/show/100734012ea5c4fed1b17562badce693.gif" alt="iOS动画篇:核心动画" width="372" height="292"></p> <p>opacity.gif</p> <p><img src="https://simg.open-open.com/show/c04dca2afd9f8cf71139e71cfaab8594.gif" alt="iOS动画篇:核心动画" width="372" height="292"></p> <p>backgroundColor.gif</p> <p><img src="https://simg.open-open.com/show/dd2807d666ff5b2d9dac939a05765d35.gif" alt="iOS动画篇:核心动画" width="372" height="292"></p> <p>cornerRadius.gif</p> <p><img src="https://simg.open-open.com/show/02edead723873cadec841e183fc5aacf.gif" alt="iOS动画篇:核心动画" width="372" height="292"></p> <p>borderWidth.gif</p> <p><img src="https://simg.open-open.com/show/126b6835fd6d766cf9b0096ebc980177.gif" alt="iOS动画篇:核心动画" width="372" height="292"></p> <p>contents.gif</p> <p><img src="https://simg.open-open.com/show/200a8934c4197d50647f30cd3d898828.gif" alt="iOS动画篇:核心动画" width="372" height="292"></p> <p>shadowColor.gif</p> <p><img src="https://simg.open-open.com/show/ed52a3caa36225570a03d1bb3a4f1e41.gif" alt="iOS动画篇:核心动画" width="372" height="292"></p> <p>shadowOffset.gif</p> <p><img src="https://simg.open-open.com/show/5a9d9e4c6260d5ded8beaa42a5dd0043.gif" alt="iOS动画篇:核心动画" width="372" height="292"></p> <p>shadowOpacity.gif</p> <p><img src="https://simg.open-open.com/show/eeade59f65b9b2b6a667cc337a07086b.gif" alt="iOS动画篇:核心动画" width="372" height="292"></p> <p>shadowRadius.gif</p> <h3>CAKeyframeAnimation</h3> <p> 可以设定keyPath起点、中间关键点(不止一个)、终点的值,每一帧所对应的时间,动画会沿着设定点进行移动。</p> <p> CAKeyframeAnimation的重要属性:</p> <p> values:关键帧数组对象,里面每一个元素即为一个关键帧,动画会在对应的时间段内,依次执行数组中每一个关键帧的动画。<br> path:动画路径对象,可以指定一个路径,在执行动画时路径会沿着路径移动,Path在动画中只会影响视图的Position。<br> keyTimes:设置关键帧对应的时间点,范围:0-1。如果没有设置该属性,则每一帧的时间平分。</p> <p> 下面以让视图绕圈为例演示其使用:</p> <p> 1、设置values使其沿正方形运动</p> <pre> <code class="language-objectivec">- (void)valueKeyframeAni { CAKeyframeAnimation * ani = [CAKeyframeAnimation animationWithKeyPath:@"position"]; ani.duration = 4.0; ani.removedOnCompletion = NO; ani.fillMode = kCAFillModeForwards; ani.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; NSValue * value1 = [NSValue valueWithCGPoint:CGPointMake(150, 200)]; NSValue *value2=[NSValue valueWithCGPoint:CGPointMake(250, 200)]; NSValue *value3=[NSValue valueWithCGPoint:CGPointMake(250, 300)]; NSValue *value4=[NSValue valueWithCGPoint:CGPointMake(150, 300)]; NSValue *value5=[NSValue valueWithCGPoint:CGPointMake(150, 200)]; ani.values = @[value1, value2, value3, value4, value5]; [self.centerShow.layer addAnimation:ani forKey:@"PostionKeyframeValueAni"]; }</code></pre> <p> 动画效果:</p> <p><img src="https://simg.open-open.com/show/fb22e4c01beeaf71be4091a1074e528c.gif" alt="iOS动画篇:核心动画" width="372" height="327"></p> <p>valueKeyframeAni.gif</p> <p> 2、设置path使其绕圆圈运动</p> <pre> <code class="language-objectivec">- (void)pathKeyframeAni { CAKeyframeAnimation * ani = [CAKeyframeAnimation animationWithKeyPath:@"position"]; CGMutablePathRef path = CGPathCreateMutable(); CGPathAddEllipseInRect(path, NULL, CGRectMake(130, 200, 100, 100)); ani.path = path; CGPathRelease(path); ani.duration = 4.0; ani.removedOnCompletion = NO; ani.fillMode = kCAFillModeForwards; [self.centerShow.layer addAnimation:ani forKey:@"PostionKeyframePathAni"]; }</code></pre> <p> 动画效果:</p> <p><img src="https://simg.open-open.com/show/043437374f6bacf8871728021c9b3a40.gif" alt="iOS动画篇:核心动画" width="372" height="327"></p> <p>pathKeyframeAni.gif</p> <h3>CATransition</h3> <p> 转场动画,比UIVIew转场动画具有更多的动画效果,比如Nav的默认Push视图的效果就是通过CATransition的kCATransitionPush类型来实现。</p> <p> CAKeyframeAnimation的重要属性:</p> <p> type:过渡动画的类型</p> <pre> <code class="language-objectivec"> type的enum值如下: kCATransitionFade 渐变 kCATransitionMoveIn 覆盖 kCATransitionPush 推出 kCATransitionReveal 揭开</code></pre> <p> 还有一些私有动画类型,效果很炫酷,不过不推荐使用。<br> 私有动画类型的值有:"cube"、"suckEffect"、"oglFlip"、 "rippleEffect"、"pageCurl"、"pageUnCurl"等等。</p> <p> subtype: 过渡动画的方向</p> <pre> <code class="language-objectivec"> subtype的enum值如下: kCATransitionFromRight 从右边 kCATransitionFromLeft 从左边 kCATransitionFromTop 从顶部 kCATransitionFromBottom 从底部</code></pre> <p> 以渐变效果为例</p> <pre> <code class="language-objectivec">- (void)transitionAni { CATransition * ani = [CATransition animation]; ani.type = kCATransitionFade; ani.subtype = kCATransitionFromLeft; ani.duration = 1.5; self.centerShow.image = [UIImage imageNamed:@"Raffle"]; [self.centerShow.layer addAnimation:ani forKey:@"transitionAni"]; }</code></pre> <p> 动画效果:</p> <p><img src="https://simg.open-open.com/show/5f755b6ae63142dce2674b11e9cc47a4.gif" alt="iOS动画篇:核心动画" width="372" height="292"></p> <p>kCATransitionFade.gif</p> <p>以下是另外三种转场类型的动画效果(图片名称对应其type类型):</p> <p><img src="https://simg.open-open.com/show/6d5dc5040660548dfc6271daba5c4bb1.gif" alt="iOS动画篇:核心动画" width="372" height="292"></p> <p>kCATransitionMoveIn.gif</p> <p><img src="https://simg.open-open.com/show/e71da4e8d872eada7e8f366a7a8079c9.gif" alt="iOS动画篇:核心动画" width="372" height="292"></p> <p>kCATransitionPush.gif</p> <p><img src="https://simg.open-open.com/show/1616afea513e03ccf1056cc5dddcc4b9.gif" alt="iOS动画篇:核心动画" width="372" height="292"></p> <p>kCATransitionReveal.gif</p> <p>以下是部分私有转场类型的动画效果(图片名称对应其type类型):</p> <p><img src="https://simg.open-open.com/show/80c51e8804232c4a1587ad8bbb58686f.gif" alt="iOS动画篇:核心动画" width="372" height="292"></p> <p>transitionAni - rippleEffect.gif</p> <p><img src="https://simg.open-open.com/show/ddea380ab2c992c5a134116c4d04fb74.gif" alt="iOS动画篇:核心动画" width="372" height="292"></p> <p>transitionAni - cube.gif</p> <p><img src="https://simg.open-open.com/show/51fbcb942e5fe54110ca67bdd43fd53d.gif" alt="iOS动画篇:核心动画" width="372" height="292"></p> <p>transitionAni - pageCurl.gif</p> <p><img src="https://simg.open-open.com/show/a919080f9d7a6c90b6fcf55368d8a6e4.gif" alt="iOS动画篇:核心动画" width="372" height="292"></p> <p>transitionAni - suckEffect.gif</p> <h3>CASpringAnimation</h3> <p> CASpringAnimation是iOS9新加入动画类型,是CABasicAnimation的子类,用于实现弹簧动画。</p> <p> CASpringAnimation的重要属性:</p> <p> mass:质量(影响弹簧的惯性,质量越大,弹簧惯性越大,运动的幅度越大)<br> stiffness:弹性系数(弹性系数越大,弹簧的运动越快)<br> damping:阻尼系数(阻尼系数越大,弹簧的停止越快)<br> initialVelocity:初始速率(弹簧动画的初始速度大小,弹簧运动的初始方向与初始速率的正负一致,若初始速率为0,表示忽略该属性)<br> settlingDuration:结算时间(根据动画参数估算弹簧开始运动到停止的时间,动画设置的时间最好根据此时间来设置)</p> <p> CASpringAnimation和UIView的SpringAnimation对比:</p> <p> 1.CASpringAnimation 可以设置更多影响弹簧动画效果的属性,可以实现更复杂的弹簧动画效果,且可以和其他动画组合。<br> 2.UIView的SpringAnimation实际上就是通过CASpringAnimation来实现。</p> <p> 以实现视图的bounds变化的弹簧动画效果为例:</p> <pre> <code class="language-objectivec">- (void)springAni { CASpringAnimation * ani = [CASpringAnimation animationWithKeyPath:@"bounds"]; ani.mass = 10.0; //质量,影响图层运动时的弹簧惯性,质量越大,弹簧拉伸和压缩的幅度越大 ani.stiffness = 5000; //刚度系数(劲度系数/弹性系数),刚度系数越大,形变产生的力就越大,运动越快 ani.damping = 100.0;//阻尼系数,阻止弹簧伸缩的系数,阻尼系数越大,停止越快 ani.initialVelocity = 5.f;//初始速率,动画视图的初始速度大小;速率为正数时,速度方向与运动方向一致,速率为负数时,速度方向与运动方向相反 ani.duration = ani.settlingDuration; ani.toValue = [NSValue valueWithCGRect:self.centerShow.bounds]; ani.removedOnCompletion = NO; ani.fillMode = kCAFillModeForwards; ani.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; [self.cartCenter.layer addAnimation:ani forKey:@"boundsAni"]; }</code></pre> <p> 动画效果:</p> <p><img src="https://simg.open-open.com/show/342e58924429de861df889218320428d.gif" alt="iOS动画篇:核心动画" width="372" height="292"></p> <p>springAni.gif</p> <h3>CAAnimationGroup</h3> <p> 使用Group可以将多个动画合并一起加入到层中,Group中所有动画并发执行,可以方便地实现需要多种类型动画的场景。</p> <p> 以实现视图的position、bounds和opacity改变的组合动画为例</p> <pre> <code class="language-objectivec">- (void)groupAni { CABasicAnimation * posAni = [CABasicAnimation animationWithKeyPath:@"position"]; posAni.toValue = [NSValue valueWithCGPoint:self.centerShow.center]; CABasicAnimation * opcAni = [CABasicAnimation animationWithKeyPath:@"opacity"]; opcAni.toValue = [NSNumber numberWithFloat:1.0]; opcAni.toValue = [NSNumber numberWithFloat:0.7]; CABasicAnimation * bodAni = [CABasicAnimation animationWithKeyPath:@"bounds"]; bodAni.toValue = [NSValue valueWithCGRect:self.centerShow.bounds]; CAAnimationGroup * groupAni = [CAAnimationGroup animation]; groupAni.animations = @[posAni, opcAni, bodAni]; groupAni.duration = 1.0; groupAni.fillMode = kCAFillModeForwards; groupAni.removedOnCompletion = NO; groupAni.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; [self.cartCenter.layer addAnimation:groupAni forKey:@"groupAni"]; }</code></pre> <p> 动画效果:</p> <p><img src="https://simg.open-open.com/show/6b5b1c53f26619ca692953879b57d7f1.gif" alt="iOS动画篇:核心动画" width="372" height="292"></p> <p>groupAni.gif</p> <h3>CATransaction</h3> <p> 最后讲一下事务,在核心动画里面存在事务(CATransaction)这样一个概念,它负责协调多个动画原子更新显示操作。<br> 简单来说事务是核心动画里面的一个基本的单元,动画的产生必然伴随着layer的Animatable属性的变化,而layer属性的变化必须属于某一个事务。<br> 因此,核心动画依赖事务。</p> <p> 事务的作用:保证一个或多个layer的一个或多个属性变化同时进行<br> 事务分为隐式和显式:<br> 1.隐式:没有明显调用事务的方法,由系统自动生成事务。比如直接设置一个layer的position属性,则会在当前线程自动生成一个事务,并在下一个runLoop中自动commit事务。<br> 2.显式:明显调用事务的方法([CATransaction begin]和[CATransaction commit])。</p> <p> CA事务的可设置属性(会覆盖隐式动画的设置):</p> <pre> <code class="language-objectivec"> animationDuration:动画时间 animationTimingFunction:动画时间曲线 disableActions:是否关闭动画 completionBlock:动画执行完毕的回调</code></pre> <p> 事务支持嵌套使用:当最外层的事务commit后动画才会开始。</p> <p> 使用实例:</p> <pre> <code class="language-objectivec"> [CATransaction begin]; [CATransaction setAnimationDuration:2.0]; [CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]]; // [CATransaction setDisableActions:YES]; //设置为YES就关闭动画 self.subLayer.bounds = self.centerShow.layer.bounds; [CATransaction commit];</code></pre> <p> 注意:只有非root layer才有隐式动画,如果你是直接设置</p> <p><code>self.cartCenter.layer.bounds = self.centerShow.layer.bounds;</code>是不会有动画效果的。</p> <p>Next</p> <p> 接下来将更新iOS绘图引擎Quartz2D</p> <p><a href="/misc/goto?guid=4959671776929246665">文/明仔Su(简书作者)</a><br> </p>