美文网首页iOS进阶iOS画图与动画
GeekBand - iOS 动画全面总结

GeekBand - iOS 动画全面总结

作者: varlarzh | 来源:发表于2016-09-19 19:12 被阅读303次

    动画 - UIKit

    动画原理

    • 视觉残留效应
    • 运动模糊

    做动画的时候要达到 60FPS 时候,画面才能流畅,不然用户会感觉界面卡顿。

    UIView 提供的动画支持

    UIView 动画本质上对 Core Animation 的封装,提供一个简洁好用的动画接口,在要求不复杂的情况下,完全可以实现很多动画。
    UIView 动画可以设置的动画属性有:

    • frame / bounds 大小变化
    • center 中心位置
    • transform 旋转平移等
    • alpha 透明度
    • backgroundColor 背景颜色
    • contentStretch 拉伸内容
    • Autolayout环境下的动画要直接修改constraint,注意 setNeedsUpdateConstraints,layoutIfNeeded的用法

    UIView 类方法动画

    • 动画的开始和结束方法
    • UIView Block动画
    • Spring 动画
    • Keyframes 动画
    • 转场动画
      • 单个视图的过渡
      • 从旧视图到新视图的过渡
    1. 动画的开始和结束方法

    基本语句:

    动画开始结束标记
    UIView.beginAnimations(String?, context: UnsafeMutablePointer<Void>)
    第一个参数是动画标识,第二个参数是附加参数,在设置了代理的情况下,此参数将发送setAnimationWillStartSelectorsetAnimationDidStopSelector 所指定的方法,一般设为 nil。

    UIView.commitAnimations() 动画结束

    动画参数设置方法

    • UIView.setAnimationDelay(NSTimeInterval) 设置动画的延时
    • UIView.setAnimationDuration(NSTimeInterval) 设置动画持续时间
    • UIView.setAnimationDelegate(AnyObject?) 设置动画代理
    • UIView.setAnimationWillStartSelector(Selector) 设置动画即将开始时代理执行的SEL
    • UIView.setAnimationDidStopSelector(Selector) 设置动画结束时代理对象执行的SEL
    • UIView.setAnimationRepeatCount(Float) 设置动画的重复次数
    • UIView.setAnimationCurve(UIViewAnimationCurve) 设置动画的曲线
    • UIView.setAnimationRepeatAutoreverses(Bool) 设置动画是否继续执行相反的动画
    • UIView.setAnimationsEnabled(Bool) 是否禁用动画效果(对象属性依然会被改变,只是没有动画效果)
    • UIView.setAnimationBeginsFromCurrentState(Bool) 设置是否从当前状态开始播放动画
      • 假设上一个动画正在播放,且尚未播放完毕,我们将要进行一个新的动画:
      • 当为true时:动画将从上一个动画所在的状态开始播放
      • 当为false时:动画将从上一个动画所指定的最终状态开始播放(此时上一个动画马上结束)

    以下是简单的例子:

    animation01
    2. UIView Block 动画

    1)最简单的 Block 动画 包含时间和动画

    UIView.animateWithDuration(NSTimeInterval) { // 动画持续时间 
                <#code#>//执行动画
            }
    

    2)带有动画完成回调的 Block 动画

    UIView.animateWithDuration(NSTimeInterval, animations: { 
                <#code#>//执行动画
                }) { (Bool) in
                    <#code#>// 动画完毕后执行的操作
            }
    

    3)可以设置延迟和过渡效果 Block 动画

     UIView.animateWithDuration(NSTimeInterval, delay: NSTimeInterval, options: UIViewAnimationOptions, animations: { 
                <#code#>
                }) { (<#Bool#>) in
                    <#code#>
            }
    

    注意,此处的 UIViewAnimationOptions 可以组合使用,在 swift 中写法 options: [UIViewAnimationOptions.CurveEaseInOut, UIViewAnimationOptions.Repeat]
    具体的枚举值,看官方文档即可。

    4)Spring 动画
    iOS7 后新增 Spring 动画,iOS 系统动画大部分采用 Spring Animation。

    UIView.animateWithDuration(NSTimeInterval, delay: NSTimeInterval, usingSpringWithDamping: CGFloat, initialSpringVelocity: CGFloat, options: UIViewAnimationOptions, animations: {
                <#code#>
                }) { (<#Bool#>) in
                    <#code#>
            }
    
    
    • Duration: 动画持续时间
    • delay: 动画执行延时
    • usingSpringWithDamping: 震动效果,范围 0~1,数值越小,震动效果越明显
    • initialSpringVelocity: 初始速度
    • options: 动画的过渡效果

    5)Keyframes 关键帧动画

    UIView.animateKeyframesWithDuration(NSTimeInterval, delay: NSTimeInterval, options: UIViewKeyframeAnimationOptions, animations: { 
                <#code#>
                }) { (<#Bool#>) in
                    <#code#>
            }
    

    增加关键帧的方法

    UIView.addKeyframeWithRelativeStartTime(Double, relativeDuration: Double, animations: { 
                    <#code#>
                })
    

    注意开始时间和持续时间均是占总时间的比例

    UIViewKeyframeAnimationOptions 的枚举值如下,可以组合使用

    UIViewAnimationOptionLayoutSubviews           //进行动画时布局子控件
    UIViewAnimationOptionAllowUserInteraction     //进行动画时允许用户交互
    UIViewAnimationOptionBeginFromCurrentState    //从当前状态开始动画
    UIViewAnimationOptionRepeat                   //无限重复执行动画
    UIViewAnimationOptionAutoreverse              //执行动画回路
    UIViewAnimationOptionOverrideInheritedDuration //忽略嵌套动画的执行时间设置
    UIViewAnimationOptionOverrideInheritedOptions //不继承父动画设置
    
    UIViewKeyframeAnimationOptionCalculationModeLinear     //运算模式 :连续
    UIViewKeyframeAnimationOptionCalculationModeDiscrete   //运算模式 :离散
    UIViewKeyframeAnimationOptionCalculationModePaced      //运算模式 :均匀执行
    UIViewKeyframeAnimationOptionCalculationModeCubic      //运算模式 :平滑
    UIViewKeyframeAnimationOptionCalculationModeCubicPaced //运算模式 :平滑均匀
    

    关键帧动画:

    private func blockAni5() {
            UIView.animateKeyframesWithDuration(5, delay: 0.0, options: UIViewKeyframeAnimationOptions.CalculationModeLinear, animations: {
                UIView.addKeyframeWithRelativeStartTime(0, relativeDuration: 1.0/4, animations: {
                    self.greenView.backgroundColor = UIColor.redColor()
                })
                
                UIView.addKeyframeWithRelativeStartTime(1.0/4, relativeDuration: 1.0/4, animations: { 
                    self.greenView.backgroundColor = UIColor.blackColor()
                    self.greenView.frame.size = CGSize(width: 50, height: 50)
                })
                
                UIView.addKeyframeWithRelativeStartTime(2.0/4, relativeDuration: 1.0/4, animations: { 
                    self.greenView.backgroundColor = UIColor.yellowColor()
                })
                UIView.addKeyframeWithRelativeStartTime(3.0/4, relativeDuration: 1.0/4, animations: { 
                    self.greenView.backgroundColor = UIColor.blueColor()
                    self.greenView.frame.size = CGSize(width: 250, height: 250)
                })
                
                
                
                }) { (_) in
                    print("动画完成blockAni5()")
            }
        }
    

    简单的例子:


    animation02
    3. UIView 转场动画

    在进行示例之前,大家需要注意一点过渡转变动画与动画属性动画的不同之处。我们在创建动画属性动画时只需要在animations闭包中添加对视图动画属性修改的代码即可,它没有作用域或作用视图的概念。而在过渡转变动画中有作用视图的概念,也就是说我们调用过渡转变动画方法时需要指定一个作用视图

    过渡转变动画中的作用视图并不是我们的目标视图,而是目标视图的容器视图,那么大家不难想象,如果该容器视图中有多个子视图,那么这些子视图都会有过渡转变动画效果。

    1)从旧视图到新视图的转场

     UIView.transitionFromView(UIView, toView: UIView, duration: NSTimeInterval, options: UIViewAnimationOptions) { (<#Bool#>) in
                <#code#>
            }
    

    在该动画过程中,fromView 会从父视图中移除,并将 toView 添加到父视图中。转场动画的作用对象是父视图,过渡效果体现在父视图上

    2)单个试图的过渡

    UIView.transitionWithView(UIView, duration: NSTimeInterval, options: UIViewAnimationOptions, animations: { 
                <#code#>
                }) { (<#Bool#>) in
                    <#code#>
            }
    

    简单的例子


    animation03

    核心动画 CoreAnimations

    Core Animation(核心动画)是一组强大的动画 API,是直接操作 CALayer 层来产生动画,相比上述的 UIView 动画,可以实现更复杂的动画效果。

    事务管理 CATransaction

    CALayer 的可用于动画的属性成为 Animatable properties,苹果官方有详细的列表,显示了所有了可以动画的属性 CALayer Animatable Properties。如果一个Layer对象对应着 View,则称这个 Layer 是一个 Root Layer, 非 Root Layer 一般是通过 CALayer 或者其子类直接创建的。
    所有的非 Root Layer 在设置 Amimation Properties 的时候都存在隐式动画,默认的 duration 是0.25秒

    事务(transaction)实际上是Core Animation用来包含一系列属性动画集合的机制,用指定事务去改变可以做动画的图层属性,不会立刻发生变化,而是提交事务时用一个动画过渡到新值。任何 Layer 的可动画属性的设置都属于某个 CATransaction,事务的作用是为了保证多个属性的变化同时进行。事务可以嵌套,当事务嵌套时候,只有最外层的事务 commit 之后,整个动画才开始。

    CATransaction没有任何实例方法,只有类型方法。CATransaction.begin()CATransaction.commit()构成了一个动画块:

    CATransaction.begin()
    /* animation block */
    CATransaction.commit()
    
    

    其他的方法

    func animationDuration() -> CFTimeInterval  // get duration, defaults to 0.25s
    func setAnimationDuration(dur: CFTimeInterval)  // set duration
    func animationTimingFunction() -> CAMediaTimingFunction?  // get timing function
    func setAnimationTimingFunction(function: CAMediaTimingFunction?)  // set timing function
    func disableActions() -> Bool  // get disable actions state
    func setDisableActions(flag: Bool)  // set disable actions state
    func completionBlock() -> (() -> Void)?  // get completion block
    func setCompletionBlock(block: (() -> Void)?)  // set completion block
    

    以上四组的方法可以用以下两个方法代替

    func valueForKey(key: String) -> AnyObject?
    func setValue(anObject: AnyObject?, forKey key: String)
    

    CATransaction 动画块只能处理CALayer相关动画,无法正确处理UIView的动画,甚至UIView的 Root layer(与UIView相关联的CALayer)也不行。
    UIView 的 Root layer动画为什么会在CATransaction动画块中失效?
    隐式动画的查找过程如下:

    禁止隐式动画:

    我们把改变属性时CALayer自动应用的动画称作行为,当CALayer的属性被修改时候,它会调用-actionForKey:方法,传递属性的名称。剩下的操作都在CALayer的头文件中有详细的说明,实质上是如下几步:

    • 图层首先检测它是否有委托,并且是否实现CALayerDelegate协议指定的-actionForLayer:forKey方法。如果有,直接调用并返回结果。
    • 如果没有委托,或者委托没有实现-actionForLayer:forKey方法,图层接着检查包含属性名称对应行为映射的actions字典。
      如果actions字典没有包含对应的属性,那么图层接着在它的style字典接着搜索属性名。
    • 最后,如果在style里面也找不到对应的行为,那么图层将会直接调用定义了每个属性的标准行为的-defaultActionForKey:方法。

    所以一轮完整的搜索结束之后,-actionForKey:要么返回空(这种情况下将不会有动画发生),要么是CAAction协议对应的对象,最后CALayer拿这个结果去对先前和当前的值做动画。

    于是这就解释了 UIKit 是如何禁用隐式动画的:每个 UIView 对它关联的图层都扮演了一个委托,并且提供了-actionForLayer:forKey 的实现方法。当不在一个动画块的实现中,UIView 对所有图层行为返回 nil,但是在动画 block 范围之内,它就返回了一个非空值。

    • UIView关联的图层禁用了隐式动画,对这种图层做动画的唯一办法就是使用UIView的动画函数(而不是依赖CATransaction),或者继承UIView,并覆盖-actionForLayer:forKey:方法,或者直接创建一个显式动画。
    • 对于单独存在的图层,我们可以通过实现图层的-actionForLayer:forKey:委托方法,或者提供一个actions字典来控制隐式动画。

    参考资料 iOS Actions

    时间系统

    参考谈谈 iOS Animation

    CAMediaTiming 协议定义了在一段动画内用来控制逝去时间的属性的集合。CALayer 通过CAMediaTiming协议实现了一个有层级关系的时间系统。

    几个重要属性(都是CALayer的属性):

    • beginTime 是相对于父级对象的开始时间
    • timeOffset是active local time的偏移量
    • speed 设置当前对象的时间流逝相对于父级对象时间流的流逝速度
    • fillMode 决定了当前对象过了非 active 时间段的行为

    显示动画

    当需要对非 Root Layer 进行动画或者需要对动画做更多的自定义的行为的时候,需要使用显示动画,基类为 CAAnimation

    核心动画类中可以直接使用的类有:

    • CABasicAnimation
    • CAKeyframeAnimation
    • CATransition
    • CAAnimationGroup
    • CASpringAnimation

    CABasicAnimation有三个比较重要的属性,fromValue,toValue,byValue,这三个属性都是可选的,但不能同时多于两个为非空.最终都是为了确定animation变化的起点和终点.中间的值都是通过插值方式计算出来的.插值计算的结果由timingFunction指定,默认timingFunction为nil,会使用liner的,也就是变化是均匀的.

    1. 核心动画类的核心方法

    • 初始化CAAnimation对象
      • 一般使用animation方法生成实例 let animation = CABasicAnimation()
      • 如果是CAPropertyAnimation的子类,可以使用'let animation = CABasicAnimation(keyPath: String?)'来生成
    • 设置动画的相关属性
      • 执行时间
      • 执行曲线
      • keyPath 的目标值
      • 代理等
    animation.duration = 2.0
    //        animation.fromValue = UIColor.blackColor()
            animation.toValue = NSValue(CGPoint: CGPointMake(300, 300))
            animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
            animation.removedOnCompletion = false
            animation.fillMode = kCAFillModeForwards
    
    • 动画的添加和移除
      • 调用 CALayer 的 view2.layer.addAnimation(animation, forKey: "color")
      • 停止动画 view2.layer.removeAnimationForKey(String)view2.layer.removeAllAnimations()

    防止动画结束后回到初始状态

    只需设置removedOnCompletion、fillMode两个属性就可以了。

    transformAnima.removedOnCompletion = NO;
    transformAnima.fillMode = kCAFillModeForwards;
    

    解释:为什么动画结束后返回原状态?

    给一个视图添加layer动画时,真正移动并不是我们的视图本身,而是 presentation layer 的一个缓存。动画开始时 presentation layer开始移动,原始layer隐藏,动画结束时,presentation layer从屏幕上移除,原始layer显示。这就解释了为什么我们的视图在动画结束后又回到了原来的状态,因为它根本就没动过。
    这个同样也可以解释为什么在动画移动过程中,我们为何不能对其进行任何操作。
    所以在我们完成layer动画之后,最好将我们的layer属性设置为我们最终状态的属性,然后将presentation layer 移除掉。

    2. 核心动画类的常用属性

    • KeyPath:可以指定 KeyPath 为 CALayer 的属性值,并对它修改,注意部分属性是不支持动画的
    • duration:动画的持续时间
    • repeatCount: 动画的重复次数
    • timingFunction:动画的时间节奏控制
    • fillMode:视图在非Active时的行为
    • removedOnCompletion:动画执行完毕后是否从图层上移除,默认为YES(视图会恢复到动画前的状态),可设置为NO(图层保持动画执行后的状态,前提是fillMode设置为kCAFillModeForwards)
    • beginTime:动画延迟执行时间(通过CACurrentMediaTime() + your time 设置)
    • delegate:代理
    func animationDidStart(anim: CAAnimation)
     func animationDidStop(anim: CAAnimation, finished flag: Bool)
    

    Timing Function对应的类是CAMediaTimingFunction,它提供了两种获得时间函数的方式,一种是使用预定义的五种时间函数,一种是通过给点两个控制点得到一个时间函数. 相关的方法为

    CAMediaTimingFunction(name: String)
    CAMediaTimingFunction(controlPoints: Float, c1y: Float, c2x: Float, c2y: Float)
    

    五种预定义的时间函数名字的常量变量分别为

    • kCAMediaTimingFunctionLinear,
    • kCAMediaTimingFunctionEaseIn,
    • kCAMediaTimingFunctionEaseOut,
    • kCAMediaTimingFunctionEaseInEaseOut,
    • kCAMediaTimingFunctionDefault

    自定义的 Timing Function 的函数图像就是一条三次的贝塞尔曲线。

    CAKeyframeAnimation动画

    两个决定动画关键帧的属性:

    • values: 关键帧数组对象,里面每一个元素就是一个关键帧,动画会在相应时间段内,依次执行数组中每一个关键帧动画
    • path: 动画路径对象,可以指定一个路径,在执行动画时会沿着路径移动,path只能对CALayer的 anchorPoint 和 position 属性起作用
    • keyTimes: 设置关键帧对应的时间点。范围0 ~ 1,默认每一帧时间平分,keyTimes数组中的每个元素定义了相应的keyframe的持续时间值作为动画的总持续时间的一小部分,每个元素的值必须大于、或等于前一个值。keyframeAni.keyTimes = [0.1,0.5,0.7,0.8,1]
    • calculationMode 计算模式,其主要针对的是每一帧的内容为一个座标点的情况,也就是对anchorPoint 和 position 进行的动画,表示插值计算的模式
      • kCAAnimationLinear 默认值 直线相连来差值
      • kCAAnimationDiscrete 离散的,不进行插值计算,所有关键帧逐个显示
      • kCAAnimationPaced 动画均匀的,此时keytimes和timeFunctions无效
      • kCAAnimationCubic 对关键帧为坐标点的关键帧进行圆滑曲线相连后插值计算,对于曲线的形状还可以通过tensionValues,continuityValues,biasValues来进行调整自定义
      • kCAAnimationCubicPaced 在kCAAnimationCubic的基础上使得动画运行变得均匀,就是系统时间内运动的距离相同,此时keyTimes以及timingFunctions也是无效的.

    简单例子


    CATransition

    转场动画,比 UIView 的转场动画具有更多的动画效果。

    CATransition的属性:

    • type: 过渡动画的类型

      • kCATransitionFade 渐变
      • kCATransitionMoveIn 覆盖
      • kCATransitionPush 推出
      • kCATransitionReveal 揭开

      私有动画类型的值有:"cube"、"suckEffect"、"oglFlip"、 "rippleEffect"、"pageCurl"、"pageUnCurl"等等

    • subtype: 过渡动画的方向

      • kCATransitionFromRight 从右边
      • kCATransitionFromLeft 从左边
      • kCATransitionFromTop 从顶部
      • kCATransitionFromBottom 从底部


    CASpringAnimation

    CASpringAnimation是iOS9新加入动画类型,是CABasicAnimation的子类,用于实现弹簧动画。

    CASpringAnimation的重要属性:

    • mass:质量(影响弹簧的惯性,质量越大,弹簧惯性越大,运动的幅度越大)
    • stiffness:弹性系数(弹性系数越大,弹簧的运动越快)
    • damping:阻尼系数(阻尼系数越大,弹簧的停止越快)
    • initialVelocity:初始速率(弹簧动画的初始速度大小,弹簧运动的初始方向与初始速率的正负一致,若初始速率为0,表示忽略该属性)
    • settlingDuration:结算时间(根据动画参数估算弹簧开始运动到停止的时间,动画设置的时间最好根据此时间来设置)
       private func springAni() {
            
            let ani = CASpringAnimation(keyPath: "bounds")
            ani.mass = 10.0 //质量,影响图层运动时的弹簧惯性,质量越大,弹簧拉伸和压缩的幅度越大
            ani.stiffness = 5000 //刚度系数(劲度系数/弹性系数),刚度系数越大,形变产生的力就越大,运动越快
            ani.damping = 100.0//阻尼系数,阻止弹簧伸缩的系数,阻尼系数越大,停止越快
            ani.initialVelocity = 5.0//初始速率,动画视图的初始速度大小;速率为正数时,速度方向与运动方向一致,速率为负数时,速度方向与运动方向相反
            ani.duration = ani.settlingDuration
            ani.toValue = NSValue(CGRect: view4.bounds)
            ani.removedOnCompletion = false
            ani.fillMode = kCAFillModeForwards
            ani.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
            view2.layer.addAnimation(ani, forKey: "boundsAni")
            
        }
    

    CAAnimationGroup

    使用Group可以将多个动画合并一起加入到层中,Group中所有动画并发执行,可以方便地实现需要多种类型动画的场景,group动画以数组表示。

    private func groupAni() {
        
        let posAni = CABasicAnimation(keyPath: "position")
        posAni.toValue = NSValue(CGPoint: CGPoint(x: 310, y: 400))
        let boundAni = CABasicAnimation(keyPath: "bounds")
        boundAni.toValue = NSValue(CGRect: CGRectMake(0, 0, 200, 200))
        let colorAni = CABasicAnimation(keyPath: "backgroundColor")
        colorAni.toValue = UIColor.redColor().CGColor
            
        let groupAni = CAAnimationGroup()
        groupAni.animations = [posAni, boundAni, colorAni]
        groupAni.duration = 1.5
        groupAni.fillMode = kCAFillModeForwards
        groupAni.removedOnCompletion = false
        groupAni.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
        view1.layer.addAnimation(groupAni, forKey: "groupAni")
            
    }
    

    文中demo的地址:Github 动画demo

    相关文章

      网友评论

      本文标题:GeekBand - iOS 动画全面总结

      本文链接:https://www.haomeiwen.com/subject/zhynettx.html