美文网首页技术
iOS出门必备之CoreAnimation(核心动画)

iOS出门必备之CoreAnimation(核心动画)

作者: TitanCoder | 来源:发表于2017-10-12 19:46 被阅读116次

    CoreAnimation

    • 前段时间接触到了一个牛逼的动画框架POP,本来想来装装逼,突然发现,苹果大大的CoreAnimation我还不会用呢!
    • 依稀记得乔帮主在2007年的WWDC大会上亲自为你演示Core Animation的强大:点击查看视频(不好意思,又装逼了)
    • 言归正传,我只是来温习一下CoreAnimation,还望路过的大神不要吐槽我太low

    GitHub项目地址

    一、Core Animation简介

    • Core Animation,中文翻译为核心动画,它是一组非常强大的动画处理API,使用它能做出非常炫丽的动画效果,而且往往是事半功倍。也就是说,使用少量的代码就可以实现非常强大的功能。
    • Core Animation可以用在Mac OS X和iOS平台。
    • Core Animation的动画执行过程都是在后台操作的,不会阻塞主线程。
    • 要注意的是,Core Animation是直接作用在CALayer上的,并非UIView
    • 通过调用CALayer的addAnimation:forKey:方法增加CAAnimation对象到CALayer中,这样就能开始执行动画了
    • 通过调用CALayer的removeAnimationForKey:方法可以停止CALayer中的动画

    二. Core Animation及其相关属性

    • 要想执行动画,就必须初始化一个CAAnimation对象。
    • 一般情况下,我们使用的比较多的是CAAnimation的子类,因此,先大致看看CAAnimation的继承结构
    • 黑线代表继承,黑色文字代表类名,白色文字代表属性。其中CAMediaTiming是一个协议(protocol)
    Core Animation结构划分.png

    需要注意的是

    • CAAnimation是所有动画类的父类,但是它不能直接使用,应该使用它的子类
    • CAPropertyAnimation也是不能直接使用的,也要使用它的子类
    • 能用的动画类只剩下4个:CABasicAnimation、CAKeyframeAnimation、CATransition、CAAnimationGroup

    常用属性

    1). removedOnCompletion:默认为true,代表动画执行完毕后就从图层上移除

    • 图形会恢复到动画执行前的状态。如果想让图层保持显示动画执行后的状态,那就设置为false,不过还要设置fillModekCAFillModeForwards

    2). timingFunction:控制动画运行的节奏

    /** timingFunction可选的值 **/
    @available(iOS 2.0, *)
    public let kCAMediaTimingFunctionLinear: String
    //1.(匀速): 在整个动画时间内动画都是以一个相同的速度来改变
    
    @available(iOS 2.0, *)
    public let kCAMediaTimingFunctionEaseIn: String
    //2. (渐进): 缓慢进入, 加速离开
    
    @available(iOS 2.0, *)
    public let kCAMediaTimingFunctionEaseOut: String
    //3. (渐出): 快速进入, 减速离开
    
    @available(iOS 2.0, *)
    public let kCAMediaTimingFunctionEaseInEaseOut: String
    //4. (渐进渐出): 缓慢进入, 中间加速, 减速离开
    
    @available(iOS 3.0, *)
    public let kCAMediaTimingFunctionDefault: String
    //5. (默认): 效果基本等同于EaseOut(渐出)
    
    

    3). fillMode决定当前对象在非active时间段的行为。

    • 要想fillMode有效,需设置removedOnCompletion = false
    • fillMode可选的值
    /* `fillMode' options. */
    @available(iOS 2.0, *)
    public let kCAFillModeForwards: String
    //1. 当动画结束后,layer会一直保持着动画最后的状态
    
    @available(iOS 2.0, *)
    public let kCAFillModeBackwards: String
    //2. 设置为该值,将会立即执行动画的第一帧,不论是否设置了 beginTime属性。观察发现,设置该值,刚开始视图不见,还不知道应用在哪里
    
    @available(iOS 2.0, *)
    public let kCAFillModeBoth: String
    //3. 该值是 kCAFillModeForwards 和 kCAFillModeBackwards的组合状态; 动画加入后开始之前,layer便处于动画初始状态,动画结束后layer保持动画最后的状态
    
    @available(iOS 2.0, *)
    public let kCAFillModeRemoved: String
    //4. 默认值,动画将在设置的 beginTime 开始执行(如没有设置beginTime属性,则动画立即执行),动画执行完成后会将layer的改变恢复原状
    

    4). delegate:动画代理,用来监听动画的执行过程

    public protocol CAAnimationDelegate : NSObjectProtocol {
    
        // 动画开始执行的时候触发这个方法
        @available(iOS 2.0, *)
        optional public func animationDidStart(_ anim: CAAnimation)
    
        // 动画执行完毕的时候触发这个方法
        @available(iOS 2.0, *)
        optional public func animationDidStop(_ anim: CAAnimation, finished flag: Bool)
    }
    

    5). 其他相关属性

    duration    动画的时长
    repeatCount    重复的次数。不停重复设置为 HUGE_VALF
    repeatDuration    设置动画的时间。在该时间内动画一直执行,不计次数。
    beginTime    指定动画开始的时间。从开始延迟几秒的话,设置为【CACurrentMediaTime() + 秒数】 的方式
    timingFunction    设置动画的速度变化
    autoreverses    动画结束时是否执行逆动画
    fromValue    所改变属性的起始值(Swift中为Any类型,OC中要包装成NSValue对象)
    toValue    所改变属性的结束时的值(类型与fromValue相同)
    byValue    所改变属性相同起始值的改变量(类型与fromValue相同)
    

    三. CABasicAnimation

    • CABasicAnimation是CAPropertyAnimation的子类,使用它可以实现一些基本的动画效果,它可以让CALayer的某个属性从某个值渐变到另一个值。下面就用CABasicAnimation实现几个简单的动画

    1. 平移动画

    方法一: 改变label的position

    let caBasic = CABasicAnimation(keyPath: "position")
    caBasic.duration = 2
    caBasic.fromValue = redLabel.layer.position
    caBasic.toValue = CGPoint(x: kScreenWidth - 50, y: 200)
    caBasic.delegate = self
    caBasic.isRemovedOnCompletion = false
    caBasic.fillMode = kCAFillModeForwards
    redLabel.layer.add(caBasic, forKey: "redLabel1")
    
    • 初始化方法中是@"position",说明要修改的是CALayer的position属性,也就是会执行平移动画
    • 默认情况下,动画执行完毕后,动画会自动从CALayer上移除,CALayer又会回到原来的状态。为了保持动画执行后的状态,可以加入第6、7行代码
    • 第8行后面的@"redLabel1"是给动画对象起个名称,以后可以调用CALayer的removeAnimationForKey:方法根据动画名称停止相应的动画
    • 遵循的代理方法
    extension ViewController: CAAnimationDelegate {
        //开始执行
        func animationDidStart(_ anim: CAAnimation) {
            print("开始动画--layer:", redLabel.layer.position)
        }
        //结束之行
        func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
            print("结束动画--layer:", redLabel.layer.position)
        }
    }
    
    
    //打印结果为:
    //开始动画--layer: (35.0, 213.0)
    //结束动画--layer: (35.0, 213.0)
    
    

    从打印信息可以看出,实际上,动画执行完毕后,并没有真正改变CALayer的position属性的值!

    方法二.

     let basic = CABasicAnimation(keyPath: "transform")
    basic.duration = 2
    let form = CATransform3DMakeTranslation(350, 400, 0)
    basic.toValue = form
    blueLabel.layer.add(basic, forKey: "blueLabel")
    

    2. 旋转动画

    let basic1 = CABasicAnimation(keyPath: "transform")
    basic1.duration = 1
    basic1.toValue = CATransform3DMakeRotation(0.25, 0, 0, 1)
    basic1.isRemovedOnCompletion = false
    basic1.fillMode = kCAFillModeForwards
    blueLabel.layer.add(basic1, forKey: "basic1")
    
    • 可以不用设置fromValue,这里只设置了toValue

    3. 缩放动画

    • CALayer的宽度从0.5倍变为2倍
    • CALayer的高度从0.5倍变为1.5倍
    let basic1 = CABasicAnimation(keyPath: "transform")
    basic1.duration = 1
    basic1.toValue = CATransform3DMakeScale(0.5, 0.5, 1)
    basic1.toValue = CATransform3DMakeScale(2, 1.5, 1)
    basic1.isRemovedOnCompletion = false
    basic1.fillMode = kCAFillModeForwards
    blueLabel.layer.add(basic1, forKey: "basic1")
    
    • CABasicAnimation虽然能够做很多基本的动画效果,但是有个局限性,只能让CALayer的属性从某个值渐变到另一个值,仅仅是在2个值之间渐变
    • 总结一些常用的animationKeyPath值的
    说明 使用形式
    transform.scale 比例转化 0.5
    transform.scale.x 宽的比例 0.5
    transform.rotation.x 围绕x轴旋转 @(M_PI_4)(OC), 0.25(Swift)
    cornerRadius 圆角的设置 30
    backgroundColor 背景颜色的变化 UIColor.purpleColor.cgColor
    bounds 大小,中心不变 CGRect
    position 位置(中心点的改变) CGPoint
    contents 内容,比如UIImageView的图片 imageAnima.toValue = UIImage(named: "toImage")?.cgImage
    opacity 透明度 0.7
    contentsRect.size.width 横向拉伸缩放 最好是0~1之间的

    四. CAKeyframeAnimation——关键帧动画

    • 关键帧动画,也是CAPropertyAnimation的子类,与CABasicAnimation的区别是:
      • CABasicAnimation只能从一个数值(fromValue)变到另一个数值(toValue)
      • CAKeyframeAnimation会使用一个Array保存这些数值
    • 属性说明:
      • values:上述的Array对象。里面的元素称为“关键帧”(keyframe)。动画对象会在指定的时间(duration)内,依次显示values数组中的每一个关键帧
      • path:可以设置一个CGPathRef、CGMutablePathRef,让图层按照路径轨迹移动。path只对CALayer的anchorPointposition起作用。如果设置了path,那么values将被忽略
      • keyTimes:可以为对应的关键帧指定对应的时间点,其取值范围为0到1.0,keyTimes中的每一个时间值都对应values中的每一帧。如果没有设置keyTimes,各个关键帧的时间是平分的
      • calculationMode: 该属性决定了物体在每个子路径下是跳着走还是匀速走,跟timeFunctions属性有点类似
        • kCAAnimationLinear默认值,表示当关键帧为座标点的时候,关键帧之间直接直线相连进行插值计算;
        • kCAAnimationDiscrete 离散的,就是不进行插值计算,所有关键帧直接逐个进行显示;
        • kCAAnimationPaced 使得动画均匀进行,而不是按keyTimes设置的或者按关键帧平分时间,此时keyTimes和timingFunctions`无效;
        • kCAAnimationCubic 对关键帧为座标点的关键帧进行圆滑曲线相连后插值计算,对于曲线的形状还可以通过tensionValues,continuityValues,biasValues来进行调整自定义主要目的是使得运行的轨迹变得圆滑;
        • kCAAnimationCubicPaced 看这个名字就知道和kCAAnimationCubic有一定联系,其实就是在kCAAnimationCubic的基础上使得动画运行变得均匀,就是系统时间内运动的距离相同,此时keyTimes以及timingFunctions也是无效的.
    • CABasicAnimation可看做是只有2个关键帧的CAKeyframeAnimation

    values方式

    let key = CAKeyframeAnimation(keyPath: "position")
    key.duration = 3
    key.repeatCount = HUGE //无线循环
    key.calculationMode = kCAAnimationPaced
    key.values = [redLabel.frame.origin, CGPoint(x: 180, y: 70), CGPoint(x: 180, y: 200), redLabel.frame.origin]
    key.keyTimes = [NSNumber(value: 0.0), NSNumber(value: 0.6), NSNumber(value: 0.7), NSNumber(value: 0.8)]
    redLabel.layer.add(key, forKey: "key")
    

    五. CASpringAnimation

    • CASpringAnimation是iOS 9 新出的
    • CASpringAnimation 继承于CABaseAnimation
    • CASpringAnimation是苹果专门解决开发者关于弹簧动画的这个需求而封装的类。

    1. CASpringAnimation相关属性

    //1. 质量,影响图层运动时的弹簧惯性,质量越大,弹簧拉伸和压缩的幅度越大, 默认值: 1
    open var mass: CGFloat
    
    //2. 刚度系数(劲度系数/弹性系数),刚度系数越大,形变产生的力就越大,运动越快(默认值: 100)
    open var stiffness: CGFloat
    
    //3. 阻尼系数,阻止弹簧伸缩的系数,阻尼系数越大,停止越快(默认值: 10)
    open var damping: CGFloat
    
    //4. 初始速率,动画视图的初始速度大小, 默认0
    //速率为正数时,速度方向与运动方向一致,速率为负数时,速度方向与运动方向相反(默认值: 0)
    open var initialVelocity: CGFloat
    
    //5. 估算时间 返回弹簧动画到停止时的估算时间,根据当前的动画参数估算(只读)
    open var settlingDuration: CFTimeInterval { get }
    
    

    2. 示例代码

    let spring = CASpringAnimation(keyPath: "position.y")
    spring.mass = 5
    spring.stiffness = 100
    spring.damping = 5
    spring.initialVelocity = 2
    spring.fromValue = blueLabel.layer.position.y
    spring.toValue = kScreenHeight - 150
    spring.duration = spring.settlingDuration
    blueLabel.layer.add(spring, forKey: "spring")
    

    六. CAAnimationGroup动画组

    • 是CAAnimation的子类,可以保存一组动画对象,将CAAnimationGroup对象加入层后,组中所有动画对象可以同时并发运行
    • 属性说明:
      • animations:用来保存一组动画对象的Array
    • 默认情况下,一组动画对象是同时运行的,也可以通过设置动画对象的beginTime属性来更改动画的开始时间

    代码示例:

    • 同时执行:平移、缩放、位移动画 -> 使用动画组
        //动画组
        fileprivate func getCAAnimationGroup(){
            //0. 初始化动画组
            let group = CAAnimationGroup()
            
            //1. 平移动画
            let basic1 = CABasicAnimation(keyPath: "position")
            basic1.fromValue = blueLabel.layer.position
            basic1.toValue = CGPoint(x: CGFloat(arc4random_uniform(200)), y: CGFloat(arc4random_uniform(500)))
            
            //2. 缩放动画
            let basic2 = CABasicAnimation(keyPath: "transform.scale")
            var scale: CGFloat = 0.1
            scale = scale < 1 ? 1.5 : 0.5
            basic2.toValue = scale
            
            //3. 旋转动画
            let basic3 = CABasicAnimation(keyPath: "transform.rotation")
            basic3.toValue = CGFloat(arc4random_uniform(360)) / 180.0
            
            //4. 添加到动画组
            group.animations = [basic1, basic2, basic3]
            //取消反弹
            group.isRemovedOnCompletion = false
            group.fillMode = kCAFillModeForwards
            group.duration = 0.5
            blueLabel.layer.add(group, forKey: "group")
        }
    
    

    七. 转场动画——CATransition

    • CATransitionCAAnimation的子类,用于做转场动画,能够为layer层提供移出屏幕和移入屏幕的动画效果。
    • iOS比Mac OS X的转场动画效果少一点
      UINavigationController就是通过CATransition实现了将控制器的视图推入屏幕的动画效果
    • 动画属性:
      • type:动画过渡类型
      • subtype:动画过渡方向
      • startProgress:动画起点(在整体动画的百分比)
      • endProgress:动画终点(在整体动画的百分比)

    1. typesubtype属性说明

    /* type类型 */
    @available(iOS 2.0, *)
    public let kCATransitionFade: String
    //交叉淡化过渡
    
    @available(iOS 2.0, *)
    public let kCATransitionMoveIn: String
    //新视图移到旧视图上面
    
    @available(iOS 2.0, *)
    public let kCATransitionPush: String
    //新视图把旧视图推出去
    
    @available(iOS 2.0, *)
    public let kCATransitionReveal: String
    //将旧视图移开,显示下面的新视图
    
    
    /* subtypes类型 */
    @available(iOS 2.0, *)
    public let kCATransitionFromRight: String
    //从右侧转场
    
    @available(iOS 2.0, *)
    public let kCATransitionFromLeft: String
    //从左侧转场
    
    @available(iOS 2.0, *)
    public let kCATransitionFromTop: String
    //从上部转场
    
    @available(iOS 2.0, *)
    public let kCATransitionFromBottom: String
    //从底部转场
    

    注意:

    • 除了上述四种效果之外,还有很多私有API效果,使用的时候要小心,可能会导致app审核不被通过
    • 使用的时候要以字符串的形式
    cube     //立方体翻滚效果
    oglFlip  //上下左右翻转效果
    suckEffect   //收缩效果,如一块布被抽走(不支持过渡方向)
    rippleEffect //滴水效果(不支持过渡方向)
    pageCurl     //向上翻页效果
    pageUnCurl   //向下翻页效果
    cameraIrisHollowOpen  //相机镜头打开效果(不支持过渡方向)
    cameraIrisHollowClose //相机镜头关上效果(不支持过渡方向)
    
    

    效果参考

    各参数动画效果.png

    2. 代码示例:

    • 展示立方体翻滚效果的图片浏览

    1. 初始化变量

    //初始化变量
    fileprivate var imageView = UIImageView(frame: UIScreen.main.bounds)
    fileprivate var currentIndex = 0
    
    

    2. 需要在viewDidLoad中调用一下方法

    //转场动画
        fileprivate func imageCATransition(){
            //0.初始化ImageView
            imageView.isUserInteractionEnabled = true
            imageView.image = UIImage(named: "0.jpg")
            view.addSubview(imageView)
            
            //1. 添加滑动手势
            let left = UISwipeGestureRecognizer(target: self, action: #selector(leftSwipe(gesture:)))
            left.direction = .left
            imageView.addGestureRecognizer(left)
            let right = UISwipeGestureRecognizer(target: self, action: #selector(rightSwipe(gesture:)))
            right.direction = .right
            imageView.addGestureRecognizer(right)
        }
    
    

    3. 滑动后执行的方法

        //MARK: 手势相关方法
        //左滑
        @objc fileprivate func leftSwipe(gesture: UIGestureRecognizer) {
            print("左滑动")
            transitionAnimation(isNext: true)
        }
        //右滑
        @objc fileprivate func rightSwipe(gesture: UIGestureRecognizer) {
            print("右滑动")
            transitionAnimation(isNext: false)
        }
        
        //设置转场动画
        fileprivate func transitionAnimation(isNext: Bool){
            let transition = CATransition()
            transition.type = kCATransitionFade
            transition.subtype = isNext ? kCATransitionFromRight : kCATransitionFromLeft
            transition.duration = 1
            imageView.image = getImage(isNext)
            imageView.layer.add(transition, forKey: "transition")
        }
        
        //获取下/上一张图片
        fileprivate func getImage(_ isNext: Bool) -> UIImage {
            currentIndex = isNext ? currentIndex + 1 : currentIndex - 1
            currentIndex = currentIndex < 0 ? 7 : currentIndex
            currentIndex = currentIndex > 7 ? 0 : currentIndex
            return UIImage(named: "\(currentIndex)" + ".jpg")!
        }
    
    

    八. 总结

    • 核心动画给我们展示的只是一个假象,layer的的frame、bounds、position并不会在动画完毕之后发生改变。
    • UIView封装的动画,会使会真实修改view的一些属性
    • 以上就是小编总结的关于Core Animation核心动画的相关分类
    • 总结的知识点比较简单, 个人感觉有点low
    • 如有不足之处,还望路过的大神多多指教

    相关文章

      网友评论

        本文标题:iOS出门必备之CoreAnimation(核心动画)

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