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

时间:2022-09-10 21:36:35

CoreAnimation

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

一、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)

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

1.需要注意的是

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

2.常用属性

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

  • CABasicAnimationCAPropertyAnimation的子类,使用它可以实现一些基本的动画效果,它可以让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无效;</li>
      <li>
      kCAAnimationCubic对关键帧为座标点的关键帧进行圆滑曲线相连后插值计算,对于曲线的形状还可以通过tensionValues,continuityValues,biasValues来进行调整自定义主要目的是使得运行的轨迹变得圆滑;</li>
      <li>
      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 //相机镜头关上效果(不支持过渡方向)

效果参考

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

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
  • 如有不足之处,还望路过的大神多多指教