iOS动画(CoreAnimation)

一、iOS核心动画介绍

核心动画框架

CoreAnimation框架是基于OpenGL与CoreGraphics图像处理框架的一个跨平台的动画框架。
在CoreAnimation中大部分的动画都是通过Layer层来实现的,通过CALayer,我们可以组织复杂的层级结构。
在CoreAnimation中,大多数的动画效果是添加在图层属性的变化上,例如,改变图层的位置,大小,颜色,圆角半径等。Layer层并不决定视图的展现,它只是存储了视图的几何属性状态。

1. 首先通过 Core Animation 类图了解下各个类之间的关系.

核心动画类组织框架

CAAnimation:核心动画的基础类,不能直接使用,负责动画运行时间、速度的控制,本身实现了CAMediaTiming协议。
CAAnimationGroup:动画组,动画组是一种组合模式设计,可以通过动画组来进行所有动画行为的统一控制,组中所有动画效果可以并发执行。
CAPropertyAnimation:属性动画的基类(通过属性进行动画设置,注意是可动画属性),不能直接使用。
CATransition:转场动画,主要通过滤镜进行动画效果设置。
CABasicAnimation:基础动画,通过属性修改进行动画参数控制,只有初始状态和结束状态。
CAKeyframeAnimation:关键帧动画,通过属性进行动画参数控制,可以有多个状态控制。
CASpringAnimation:弹簧动画,通过属性进行动画参数控制,可以有多个状态控制。但是9.0才有的属性。

基础动画、关键帧动画都属于属性动画,就是通过修改属性值产生动画效果,开发人员只需要设置初始值和结束值,中间的过程动画(又叫“补间动画”)由系统自动计算产生。和基础动画不同的是关键帧动画可以设置多个属性值,每两个属性中间的补间动画由系统自动完成。

2.接下来详细介绍下动画的各个属性及作用

  • fromValue : 动画的开始值(Any类型, 根据动画不同可以是CGPoint、NSNumber等)

  • toValue: 动画的结束值, 和fromValue类似

  • beginTime: 动画的开始时间

  • duration : 动画的持续时间

  • repeatCount : 动画的重复次数

  • fillMode: 动画的运行场景

  • isRemovedOnCompletion: 完成后是否删除动画

  • autoreverses: 执行的动画按照原动画返回执行

  • path:关键帧动画中的执行路径

  • values: 关键帧动画中的关键点数组

  • animations: 组动画中的动画数组

  • delegate : 动画代理, 封装了动画的执行和结束方法

  • timingFunction: 控制动画的显示节奏, 系统提供五种值选择,分别是:

    1. kCAMediaTimingFunctionDefault( 默认,中间快)
    2. kCAMediaTimingFunctionLinear (线性动画)
    3. kCAMediaTimingFunctionEaseIn (先慢后快 慢进快出)
    4. kCAMediaTimingFunctionEaseOut (先块后慢快进慢出)
    5. kCAMediaTimingFunctionEaseInEaseOut (先慢后快再慢)
  • type: 过渡动画的动画类型,系统提供了多种过渡动画, 分别是:

    1. fade (淡出 默认)
    2. moveIn (覆盖原图)
    3. push (推出)
    4. fade (淡出 默认)
    5. reveal (底部显示出来)
    6. cube (立方旋转)
    7. suck (吸走)
    8. oglFlip (水平翻转 沿y轴)
    9. ripple (滴水效果)
    10. curl (卷曲翻页 向上翻页)
    11. unCurl (卷曲翻页返回 向下翻页)
    12. caOpen (相机开启)
    13. caClose (相机关闭)
  • subtype : 过渡动画的动画方向, 系统提供了四种,分别是:

    1. fromLeft( 从左侧)
    2. fromRight (从右侧)
    3. fromTop (有上面)
    4. fromBottom (从下面)
  • mass: 质量,影响图层运动时的弹簧惯性,质量越大,弹簧拉伸和压缩的幅度越大

  • stiffness:弹簧动画的刚度系数(劲度系数/弹性系数),刚度系数越大,形变产生的力就越大,运动越快

  • damping:阻尼系数,阻止弹簧伸缩的系数,阻尼系数越大,停止越快

  • initialVelocity:初始速率,动画视图的初始速度大小
    速率为正数时,速度方向与运动方向一致,速率为负数时,速度方向与运动方向相反

  • settlingDuration:结算时间 返回弹簧动画到停止时的估算时间,根据当前的动画参数估算
    通常弹簧动画的时间使用结算时间比较准确

二、Core Animation的使用

1. 基础动画(CABasicAnimation

基础动画主要提供了对于CALayer对象中的可变属性进行简单动画的操作。比如:位移、旋转、缩放、透明度、背景色等。
基础动画根据 keyPath 来区分不同的动画, 系统提供了多个类型,如:
基础动画根据 keyPath 来区分不同的动画, 系统提供了多个类型,如: transform.scale (比例转换)、transform.scale.xtransform.scale.ytransform.rotation(旋转) 、transform.rotation.x(绕x轴旋转)、transform.rotation.y(绕y轴旋转)、transform.rotation.z(绕z轴旋转)、opacity (透明度)、marginbackgroundColor(背景色)、cornerRadius(圆角)、borderWidth(边框宽)、boundscontentscontentsRectcornerRadiusframehiddenmaskmasksToBoundsshadowColor(阴影色)、shadowOffsetshadowOpacity, 在使用时候, 需要根据具体的需求选择合适的。

旋转.gif
  • 位移动画:
 func positionAnimation() {
        
        let animation = CABasicAnimation.init(keyPath: "position") //keyPath为系统提供
        animation.fromValue = CGPoint.init(x: margin_ViewMidPosition, y: kScreenH / 2 - margin_Top)
        animation.toValue = CGPoint.init(x: kScreenW - margin_ViewMidPosition, y: kScreenH / 2 - margin_Top)
        animation.duration = 1.0
        view_Body.layer.add(animation, forKey: "positionAnimation") //key自定义
}
  • 旋转动画:
func rotateAnimation() {
        
        let animation = CABasicAnimation.init(keyPath: "transform.rotation.z")
        animation.toValue = NSNumber.init(value: Double.pi)
        animation.duration = 0.1
        animation.repeatCount = 1e100 //无限大重复次数
        view_Body.layer.add(animation, forKey: "rotateAnimation")
 }
  • 缩放动画:
 func scaleAnimation() {
        
        let animation = CABasicAnimation.init(keyPath: "transform.scale")
        animation.toValue = NSNumber.init(value: 2.0)
        animation.duration = 1.0
        view_Body.layer.add(animation, forKey: "scaleAnimation")
}
  • 透明度动画:
func opacityAnimation() {
        
        let animation = CABasicAnimation.init(keyPath: "opacity")
        animation.fromValue = NSNumber.init(value: 1.0)
        animation.toValue = NSNumber.init(value: 0.0)
        animation.duration = 1.0
        view_Body.layer.add(animation, forKey: "opacityAnimation")
}
  • 背景色动画
 func backgroundColorAnimation() {
        
        let animation = CABasicAnimation.init(keyPath: "backgroundColor")
        animation.toValue = UIColor.green.cgColor //因为layer层动画, 所以需要使用cgColor
        animation.duration = 1.0
        view_Body.layer.add(animation, forKey: "backgroundColorAnimation")
    }

2. 关键帧动画( CAKeyframeAnimation )

CAKeyframeAnimationCABasicAnimation 都属于CAPropertyAnimatin 的子类。不同的是 CABasicAnimation 只能从一个数值(fromValue)变换成另一个数值(toValue),而 CAKeyframeAnimation 则会使用一个数组(values) 保存一组关键帧, 也可以给定一个路径(path)制作动画。

CAKeyframeAnimation主要有 三个 重要属性:

  • values:存放关键帧(keyframe)的数组,动画对象会在指定的时间(duration)内,依次显示values数组中的每一个关键帧 .
  • path:可以设置一个 CGPathRefCGMutablePathRef,让层跟着路径移动. path 只对 CALayer 的 anchorPointposition 起作用, 如果设置了path,那么values将被忽略.
  • keyTimes:可以为对应的关键帧指定对应的时间点,其取值范围为0到1.0, keyTimes 中的每一个时间值都对应 values 中的每一帧.当 keyTimes 没有设置的时候,各个关键帧的时间是根据 duration 平分的。

以抖动截图为例, 效果图如下:

抖动.gif

动画代码如下:

  • 关键帧动画
func keyFrameAnimation() {
        
        let animation = CAKeyframeAnimation.init(keyPath: "position")
        let value_0 = CGPoint.init(x: margin_ViewMidPosition, y: kScreenH / 2 - margin_ViewWidthHeight)
        let value_1 = CGPoint.init(x: kScreenW / 3, y: kScreenH / 2 - margin_ViewWidthHeight)
        let value_2 = CGPoint.init(x: kScreenW / 3, y: kScreenH / 2 + margin_ViewMidPosition)
        let value_3 = CGPoint.init(x: kScreenW * 2 / 3, y: kScreenH / 2 + margin_ViewMidPosition)
        let value_4 = CGPoint.init(x: kScreenW * 2 / 3, y: kScreenH / 2 - margin_ViewWidthHeight)
        let value_5 = CGPoint.init(x: kScreenW - margin_ViewMidPosition, y: kScreenH / 2 - margin_ViewWidthHeight)
        animation.values = [value_0, value_1, value_2, value_3, value_4, value_5]
        animation.duration = 2.0
        view_Body.layer.add(animation, forKey: "keyFrameAnimation")
}
  • 路径动画
func pathAnimation() {
        
        let animation = CAKeyframeAnimation.init(keyPath: "position")
        let path = UIBezierPath.init(arcCenter: CGPoint.init(x: kScreenW / 2, y: kScreenH / 2), radius: 60, startAngle: 0.0, endAngle: .pi * 2, clockwise: true)
        animation.duration = 2.0
        animation.path = path.cgPath
        view_Body.layer.add(animation, forKey: "pathAnimation")
}
  • 抖动动画
func shakeAnimation() {
        
        let animation = CAKeyframeAnimation.init(keyPath: "transform.rotation")
        let value_0 = NSNumber.init(value: -Double.pi / 180 * 8)
        let value_1 = NSNumber.init(value: Double.pi / 180 * 8)
        animation.values = [value_0, value_1, value_0]
        animation.duration = 1.0
        animation.repeatCount = 1e100
        view_Body.layer.add(animation, forKey: "shakeAnimation")
}

4. 弹簧动画( CASpringAnimation )

iOS9才引入的动画类,它继承于CABaseAnimation,用于制作弹簧动画,先演示个例子

弹簧动画.gif

动画代码如下:

func springAnimation() {
        CASpringAnimation *spring = CASpringAnimation(keyPath: @"position.x");
        spring.damping = 5;
        spring.stiffness = 100;
        spring.mass = 1;
        spring.initialVelocity = 0;
        spring.fromValue = label.layer.position.x;
        spring.toValue = label.layer.position.x + 50;
        spring.duration = spring.settlingDuration;
        label.layer.addAnimation(spring, forKey: spring.keyPath);
}

把 label 替换成你要进行动画的视图,然后完成一个弹簧动画了

3. 组动画( CAAnimationGroup )

CAAnimationGroupCAAnimation 的子类,可以保存一组动画对象,可以保存基础动画、关键帧动画等,数组中所有动画对象可以同时并发运行, 也可以通过实践设置为串行连续动画.

效果截图如下:

组动画.gif

动画代码如下:

  • 同时
//同时
func sameTimeAnimation() {
        
        let animation_Position = CAKeyframeAnimation.init(keyPath: "position")
        let value_0 = CGPoint.init(x: margin_ViewMidPosition, y: kScreenH / 2 - margin_ViewMidPosition)
        let value_1 = CGPoint.init(x: kScreenW / 3, y: kScreenH / 2 - margin_ViewMidPosition)
        let value_2 = CGPoint.init(x: kScreenW / 3, y: kScreenH / 2 + margin_ViewMidPosition)
        let value_3 = CGPoint.init(x: kScreenW / 3 * 2, y: kScreenH / 2 + margin_ViewMidPosition)
        let value_4 = CGPoint.init(x: kScreenW / 3 * 2, y: kScreenH / 2 - margin_ViewMidPosition)
        let value_5 = CGPoint.init(x: kScreenW - margin_ViewMidPosition, y: kScreenH / 2 - margin_ViewMidPosition)
        animation_Position.values = [value_0, value_1, value_2, value_3, value_4, value_5]
        
        let animation_BGColor = CABasicAnimation.init(keyPath: "backgroundColor")
        animation_BGColor.toValue = UIColor.green.cgColor
        
        let animation_Rotate = CABasicAnimation.init(keyPath: "transform.rotation")
        animation_Rotate.toValue = NSNumber.init(value: Double.pi * 4)
        
        let animation_Group = CAAnimationGroup()
        animation_Group.animations = [animation_Position, animation_BGColor, animation_Rotate]
        animation_Group.duration = 4.0
        view_Body.layer.add(animation_Group, forKey: "groupAnimation")
 }
  • 连续
 //连续动画 最主要的是处理好各个动画时间的衔接
    func goOnAnimation() {
        
        //定义一个动画开始的时间
        let currentTime = CACurrentMediaTime()
        
        let animation_Position = CABasicAnimation.init(keyPath: "position")
        animation_Position.fromValue = CGPoint.init(x: margin_ViewMidPosition, y: kScreenH / 2)
        animation_Position.toValue = CGPoint.init(x: kScreenW / 2, y: kScreenH / 2)
        animation_Position.duration = 1.0
        animation_Position.fillMode = "forwards" //只在前台
        animation_Position.isRemovedOnCompletion = false //切出界面再回来动画不会停止
        animation_Position.beginTime = currentTime
        view_Body.layer.add(animation_Position, forKey: "positionAnimation")
        
        let animation_Scale = CABasicAnimation.init(keyPath: "transform.scale")
        animation_Scale.fromValue = NSNumber.init(value: 0.7)
        animation_Scale.toValue = NSNumber.init(value: 2.0)
        animation_Scale.duration = 1.0
        animation_Scale.fillMode = "forwards"
        animation_Scale.isRemovedOnCompletion = false
        animation_Scale.beginTime = currentTime + 1.0
        view_Body.layer.add(animation_Scale, forKey: "scaleAnimation")
        
        let animation_Rotate = CABasicAnimation.init(keyPath: "transform.rotation")
        animation_Rotate.toValue = NSNumber.init(value: Double.pi * 4)
        animation_Rotate.duration = 1.0
        animation_Rotate.fillMode = "forwards"
        animation_Rotate.isRemovedOnCompletion = false
        animation_Rotate.beginTime = currentTime + 2.0
        view_Body.layer.add(animation_Rotate, forKey: "rotateAnimation")
    }

4. 过渡动画( CATransition )

CATransitionCAAnimation 的子类,用于做过渡动画或者 转场 动画,能够为层提供移出屏幕和移入屏幕的动画效果。

过渡动画通过 type 设置不同的动画效果, CATransition 有多种过渡效果, 但其实 Apple 官方的SDK只提供了四种:

  • fade 淡出 默认
  • moveIn 覆盖原图
  • push 推出
  • reveal 底部显示出来

私有API提供了其他很多非常炫的过渡动画,如 cube(立方旋转)、suckEffect(吸走)、oglFlip(水平翻转 沿y轴)、 rippleEffect(滴水效果)、pageCurl(卷曲翻页 向上翻页)、pageUnCurl(卷曲翻页 向下翻页)、cameraIrisHollowOpen(相机开启)、cameraIrisHollowClose(相机关闭)等。
注: 因 Apple 不提供维护,并且有可能造成你的app审核不通过, 所以不建议开发者们使用这些私有API.

效果如下:


过渡动画.gif

翻页动画代码如下:

 func curlAnimation() {
        
        let animation_Curl = CATransition()
        animation_Curl.type = "pageCurl"
        animation_Curl.subtype = "fromRight"
        animation_Curl.duration = 1.0
        view_Body.layer.add(animation_Curl, forKey: "curlAnimation")
    }

三、总结

看完整篇文章相信你对 iOS 中的动画有了一个详细的了解, 其实单个动画都是比较简单的, 而复杂的动画其实都是由一个个简单的动画组装而成的,所以遇到比较难得动画需求, 我们只要充分组装不同的动画,就能实现出满意的效果.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270