Core Animation总结(四)

Core Animation总结(一)图层变换(平面 立体)

Core Animation总结(二)专用图层

Core Animation总结(三)动画

Core Animation总结(四)

Core Animation总结(五)性能

Core Animation总结(六)知识点整理

#Core Animation

###图层时间 CAMediaTiming协议定义了在一段动画内用来控制逝去时间的属性的集合,CALayer和CAAnimation都实现了这个协议,所以时间可以被任意基于一个图层或者一段动画的类控制。

duration(CAMediaTiming的属性之一)是一个CFTimeInterval的类型(类似于NSTimeInterval的一种双精度浮点类型),对将要进行的动画的一次迭代指定了时间。

CAMediaTiming另外还有一个属性叫做repeatCount,代表动画重复的迭代次数。如果duration是2,repeatCount设为3.5(三个半迭代),那么完整的动画时长将是7秒。

duration和repeatCount默认都是0。但这不意味着动画时长为0秒,或者0次,这里的0仅仅代表了“默认”,也就是0.25秒和1次

let animation: CABasicAnimation = CABasicAnimation()
animation.keyPath = "transform.rotation"
// 持续时间
animation.duration = 2.0
// 次数
animation.repeatCount = 2
animation.byValue = CGFloat.pi*2
shipLayer.add(animation, forKey: "rotateAnimation")

创建重复动画的另一种方式是使用repeatDuration属性,它让动画重复一个指定的时间,而不是指定次数。你甚至设置一个叫做autoreverses的属性(BOOL类型)在每次间隔交替循环过程中自动回放。这对于播放一段连续非循环的动画很有用

开门关门的效果

shipLayer = CALayer()
shipLayer.frame = CGRect(x: 100, y: 100, width: 150, height: 200)
shipLayer.position = CGPoint(x: 150, y: 200)
shipLayer.anchorPoint = CGPoint(x: 0, y: 0.5)
shipLayer.contents = UIImage(named: "bg.jpg")?.cgImage
shipLayer.borderWidth=1
self.view.layer.addSublayer(shipLayer)

// 透视效果
var transform: CATransform3D = CATransform3DIdentity
// 首先要实现view(layer)的透视效果(就是近大远小),是通过设置m34的:m34= -1/D,D越小透视效果越明显。
transform.m34 = -1/500
self.view.layer.sublayerTransform = transform

let animation: CABasicAnimation = CABasicAnimation()
animation.keyPath = "transform.rotation.y"
animation.toValue = -CGFloat.pi/2
animation.duration = 2
/*
 repeatDuration让动画重复一个指定的时间,而不是指定次数
 把repeatDuration设置为INFINITY,于是动画无限循环播放,设置repeatCount为INFINITY也有同样的效果
 repeatCount和repeatDuration可能会相互冲突,所以你只要对其中一个指定非零值
 */
animation.repeatDuration = 2
//每次间隔交替循环过程中自动回放
animation.autoreverses = true
shipLayer.add(animation, forKey: nil)

beginTime指定了动画开始之前的的延迟时间。这里的延迟从动画添加到可见图层的那一刻开始测量,默认是0 speed是一个时间的倍数,默认1.0,减少它会减慢图层/动画的时间,增加它会加快速度。如果2.0的速度,那么对于一个duration为1的动画,实际上在0.5秒的时候就已经完成了 timeOffset和beginTime类似,但是和增加beginTime导致的延迟动画不同,增加timeOffset只是让动画快进到某一点,例如,对于一个持续1秒的动画来说,设置timeOffset为0.5意味着动画将从一半的地方开始 把speed设为2.0,把timeOffset设置为0.5,那么你的动画将从动画最后结束的地方开始,因为1秒的动画实际上被缩短到了0.5秒。然而即使使用了timeOffset让动画从结束的地方开始,它仍然播放了一个完整的时长,这个动画仅仅是循环了一圈,然后从头开始播放。

let v: UIView = UIView(frame: CGRect(x: 50, y: 50, width: 100, height: 100))
v.backgroundColor = UIColor.blue
self.view.addSubview(v)

// 贝塞尔曲线
let bezierPath: UIBezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 150, y: 0))
bezierPath.addCurve(to: CGPoint(x: 150, y: 300), controlPoint1: CGPoint(x: 0, y: 75), controlPoint2: CGPoint(x: 300, y: 225))

let pathLayer: CAShapeLayer = CAShapeLayer()
pathLayer.path = bezierPath.cgPath
pathLayer.fillColor = UIColor.clear.cgColor
pathLayer.strokeColor = UIColor.red.cgColor
pathLayer.lineWidth = 3
v.layer.addSublayer(pathLayer)

let shipLayer: CALayer = CALayer()
shipLayer.frame = CGRect(x: 0, y: 0, width: 50, height: 50)
shipLayer.position = CGPoint(x: 0, y: 150)
shipLayer.contents = UIImage(named: "bg.jpg")?.cgImage
v.layer.addSublayer(shipLayer)

let animation: CAKeyframeAnimation = CAKeyframeAnimation()
animation.path = bezierPath.cgPath
animation.keyPath = "position"
/*
 beginTime指定了动画开始之前的的延迟时间。
 这里的延迟从动画添加到可见图层的那一刻开始测量,默认是0(就是说动画会立刻执行)。
 */
animation.beginTime = 1
/*
 增加timeOffset只是让动画快进到某一点,timeOffset并不受speed的影响
 例如,
 对于一个持续duration = 4秒的动画来说,设置timeOffset为2
 意味着动画将从一半的地方开始,到结束,然后从开始的地方到一半的地方,仍然播放了一个完整的时长
 */
animation.timeOffset = 2 // CFTimeInterval
/*
 speed是一个时间的倍数,默认1.0,减少它会减慢图层/动画的时间,增加它会加快速度。
 如果把图层的speed设置成0,它会暂停任何添加到图层上的动画。类似的,设置speed大于1.0将会快进,设置成一个负值将会倒回动画。
 如果2.0的速度,那么对于一个duration为1的动画,实际上在0.5秒的时候就已经完成了。
 */
animation.speed = 1.0 // float
animation.duration = 4
//kCAAnimationRotateAuto,图层将会根据曲线的切线自动旋转
animation.rotationMode = kCAAnimationRotateAuto
//removeOnCompletion被设置为NO的动画将会在动画结束的时候仍然保持之前的状态
animation.isRemovedOnCompletion = false
/*
 默认是kCAFillModeRemoved,当动画不再播放的时候就显示图层模型指定的值剩下的三种类型向前,向后或者即向前又向后去填充动画状态,使得动画在开始前或者结束后仍然保持开始和结束那一刻的值。
 使用时,需要 isRemovedOnCompletion = false
 */
animation.fillMode = kCAFillModeRemoved
shipLayer.add(animation, forKey: nil)

对CALayer或者CAGroupAnimation调整duration和repeatCount/repeatDuration属性并不会影响到子动画。 但是beginTime,timeOffset和speed属性将会影响到子动画。 然而在层级关系中,beginTime指定了父图层开始动画(或者组合关系中的父动画)和对象将要开始自己动画之间的偏移。 调整CALayer和CAGroupAnimation的speed属性将会对动画以及子动画速度应用一个缩放的因子。

#####全局时间和本地时间 CoreAnimation有一个全局时间的概念,也就是所谓的马赫时间(“马赫”实际上是iOS和Mac OS系统内核的命名)。马赫时间在设备上所有进程都是全局的,真实的作用在于对动画的时间测量提供了一个相对值。注意当设备休眠的时候马赫时间会暂停,也就是所有的CAAnimations(基于马赫时间)同样也会暂停。使用 CACurrentMediaTime 函数来访问马赫时间

每个CALayer和CAAnimation实例都有自己本地时间的概念,是根据父图层/动画层级关系中的beginTime,timeOffset和speed属性计算。就和转换不同图层之间坐标关系一样,CALayer同样也提供了方法来转换不同图层之间的本地时间

open func convertTime(_ t: CFTimeInterval, from l: CALayer?) -> CFTimeInterval
open func convertTime(_ t: CFTimeInterval, to l: CALayer?) -> CFTimeInterval

###缓冲 设置CAAnimation的timingFunction属性,是CAMediaTimingFunction类的一个对象。如果想改变隐式动画的计时函数,同样也可以使用CATransaction的+setAnimationTimingFunction:方法。

这里有一些方式来创建CAMediaTimingFunction,最简单的方式是调用+timingFunctionWithName:的构造方法。

var v: UIView!
var layer: CALayer!
override func viewDidLoad() {
    super.viewDidLoad()
    
    self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapAction(sender:))))
    
    v = UIView(frame: CGRect(x: 30, y: 30, width: 50, height: 50))
    v.backgroundColor = UIColor.gray
    self.view.addSubview(v)
    
    layer = CALayer()
    layer.frame = CGRect(x: 50, y: 50, width: 100, height: 100)
    layer.opacity = 0.3
    layer.backgroundColor = UIColor.red.cgColor
    self.view.layer.addSublayer(layer)
}
func tapAction(sender:UITapGestureRecognizer){
    /*
    // 1.CALayer 动画缓冲
    // 开始一个新的事务
    CATransaction.begin()
    // 持续时间
    CATransaction.setAnimationDuration(2.0)
    /*
     CAAnimation的timingFunction属性,是CAMediaTimingFunction类的一个对象
     kCAMediaTimingFunctionLinear 默认函数 一个线性的计时函数 线性步调对于那些立即加速并且保持匀速到达终点的场景会有意义(例如射出枪膛的子弹)
     kCAMediaTimingFunctionEaseIn常量创建了一个慢慢加速然后突然停止的方法。对于之前提到的自由落体的例子来说很适合,或者比如对准一个目标的导弹的发射。
     kCAMediaTimingFunctionEaseOut它以一个全速开始,然后慢慢减速停止。它有一个削弱的效果,应用的场景比如一扇门慢慢地关上
     kCAMediaTimingFunctionEaseInEaseOut创建了一个慢慢加速然后再慢慢减速的过程
     kCAMediaTimingFunctionDefault,它和kCAMediaTimingFunctionEaseInEaseOut很类似,但是加速和减速的过程都稍微有些慢。它和kCAMediaTimingFunctionEaseInEaseOut的区别很难察觉,当创建显式的CAAnimation它并不是默认选项
     */
    CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut ))
    layer.position = sender.location(in: self.view)
    // 提交事务
    CATransaction.commit()
    */
    
    
    // 2.关键帧动画缓冲 给图层的颜色变化添加一点脉冲效果
    let animation: CAKeyframeAnimation = CAKeyframeAnimation()
    animation.keyPath = "backgroundColor"
    animation.duration = 4.0
    animation.values = [
        UIColor.red.cgColor,
        UIColor.green.cgColor,
        UIColor.blue.cgColor,
        UIColor.gray.cgColor,
        UIColor.red.cgColor
    ]
    let fn: CAMediaTimingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
    /*
     CAKeyframeAnimation有一个NSArray类型的timingFunctions属性,
     我们可以用它来对每次动画的步骤指定不同的计时函数。
     但是指定函数的个数一定要等于keyframes数组的元素个数减一,因为它是描述每一帧之间动画速度的函数。
     */
    animation.timingFunctions = [fn,fn,fn,fn]
    layer.add(animation, forKey: nil)
    
    
    
    
    // 3.UIView 动画缓冲
    /*
     UIKit的动画也同样支持这些缓冲方法的使用,尽管语法和常量有些不同,
     为了改变UIView动画的缓冲选项,参数添加options
     */
    UIView.animate(
        withDuration: 2.0,
        delay: 0.0,
        options: UIViewAnimationOptions.curveEaseOut,
        animations: {
            self.v.center = sender.location(in: self.view)
        },
        completion: {(b:Bool) in
    })
}

#####三次贝塞尔曲线 CAMediaTimingFunction函数的主要原则在于它把输入的时间转换成起点和终点之间成比例的改变。 x轴代表时间,y轴代表改变的量,于是线性的缓冲就是一条从起点开始的简单的斜线(x=y,x>=0) CAMediaTimingFunction使用了一个叫做三次贝塞尔曲线的函数,一个三次贝塞尔曲线通过四个点来定义,第一个和最后一个点代表了曲线的起点和终点,剩下中间两个点叫做控制点,因为它们控制了曲线的形状,贝塞尔曲线的控制点其实是位于曲线之外的点,也就是说曲线并不一定要穿过它们。

使用UIBezierPath绘制CAMediaTimingFunction

//曲线的起始和终点始终是{0, 0}和{1, 1},于是我们只需要检索曲线的第二个和第三个点(控制点)
//create timing function
CAMediaTimingFunction *function = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut];
//get control points
CGPoint controlPoint1, controlPoint2;
//用来检索曲线的点
[function getControlPointAtIndex:1 values:(float *)&controlPoint1];
[function getControlPointAtIndex:2 values:(float *)&controlPoint2];
//用UIBezierPath和CAShapeLayer来把它画出来
//create curve
UIBezierPath *path = [[UIBezierPath alloc] init];
[path moveToPoint:CGPointZero];
[path addCurveToPoint:CGPointMake(1, 1) controlPoint1:controlPoint1 controlPoint2:controlPoint2];
//scale the path up to a reasonable size for display
[path applyTransform:CGAffineTransformMakeScale(200, 200)];
//create shape layer
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.strokeColor = [UIColor redColor].CGColor;
shapeLayer.fillColor = [UIColor clearColor].CGColor;
shapeLayer.lineWidth = 4.0f;
shapeLayer.path = path.CGPath;
[self.layerView.layer addSublayer:shapeLayer];
//flip geometry so that 0,0 is in the bottom-left
self.layerView.layer.geometryFlipped = YES;

#####基于关键帧的缓冲 使用关键帧实现反弹球的动画

var v: UIView!
var layer: CALayer!
override func viewDidLoad() {
    super.viewDidLoad()
    self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapAction(sender:))))
    
    v = UIView(frame: CGRect(x: 50, y: 50, width: 100, height: 100))
    v.backgroundColor = UIColor.gray
    self.view.addSubview(v)
}
func tapAction(sender:UITapGestureRecognizer){
    v.center = CGPoint(x: 150, y: 50)
    //使用关键帧实现反弹球的动画
    let animation: CAKeyframeAnimation = CAKeyframeAnimation()
    animation.keyPath = "position"
    animation.duration = 2
    animation.values = [
        CGPoint(x: 150, y: 50),
        CGPoint(x: 150, y: 200),
        CGPoint(x: 150, y: 100),
        CGPoint(x: 150, y: 200),
        CGPoint(x: 150, y: 150),
        CGPoint(x: 150, y: 200),
        CGPoint(x: 150, y: 180),
        CGPoint(x: 150, y: 200)
    ]
    //指定函数的个数一定要等于keyframes数组的元素个数减一,因为它是描述每一帧之间动画速度的函数。
    animation.timingFunctions = [
        CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn),
        CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut),
        CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn),
        CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut),
        CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn),
        CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut),
        CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
    ]
    //keyTimes来指定每个关键帧的时间偏移,由于每次反弹的时间都会减少,于是关键帧并不会均匀分布
    animation.keyTimes = [
        0.0, 0.3, 0.5, 0.7, 0.8, 0.9, 0.95, 1.0
    ]
    v.layer.position = CGPoint(x: 150, y: 200)
    v.layer.add(animation, forKey: nil)
}

###基于定时器的动画

#####定时帧

NSTimer iOS上的每个线程都管理了一个NSRunloop,字面上看就是通过一个循环来完成一些任务列表。但是对主线程,这些任务包含如下几项:

  • 处理触摸事件
  • 发送和接受网络数据包
  • 执行使用gcd的代码
  • 处理计时器行为
  • 屏幕重绘

当你设置一个NSTimer,他会被插入到当前任务列表中,然后直到指定时间过去之后才会被执行。但是何时启动定时器并没有一个时间上限,而且它只会在列表中上一个任务完成之后开始执行。这通常会导致有几毫秒的延迟,但是如果上一个任务过了很久才完成就会导致延迟很长一段时间。 屏幕重绘的频率是一秒钟六十次,但是和定时器行为一样,如果列表中上一个执行了很长时间,它也会延迟。这些延迟都是一个随机值,于是就不能保证定时器精准地一秒钟执行六十次。有时候发生在屏幕重绘之后,这就会使得更新屏幕会有个延迟,看起来就是动画卡壳了。有时候定时器会在屏幕更新的时候执行两次,于是动画看起来就跳动了。

CADisplayLink CADisplayLink是CoreAnimation提供的另一个类似于NSTimer的类,它总是在屏幕完成一次更新之前启动,它的接口设计的和NSTimer很类似,所以它实际上就是一个内置实现的替代,但是和timeInterval以秒为单位不同,CADisplayLink有一个整型的frameInterval属性,指定了间隔多少帧之后才执行。默认值是1,意味着每次屏幕更新之前都会执行一次。但是如果动画的代码执行起来超过了六十分之一秒,你可以指定frameInterval为2,就是说动画每隔一帧执行一次(一秒钟30帧)或者3,也就是一秒钟20次,等等。 用CADisplayLink而不是NSTimer,会保证帧率足够连续,使得动画看起来更加平滑,但即使CADisplayLink也不能保证每一帧都按计划执行,一些失去控制的离散的任务或者事件(例如资源紧张的后台程序)可能会导致动画偶尔地丢帧。当使用NSTimer的时候,一旦有机会计时器就会开启,但是CADisplayLink却不一样:如果它丢失了帧,就会直接忽略它们,然后在下一次更新的时候接着运行。

帧的持续时间 无论是使用NSTimer还是CADisplayLink,我们仍然需要处理一帧的时间超出了预期的六十分之一秒。由于我们不能够计算出一帧真实的持续时间,所以需要手动测量。我们可以在每帧开始刷新的时候用CACurrentMediaTime()记录当前时间,然后和上一帧记录的时间去比较。我们就可以得到真实的每帧持续的时间,然后代替硬编码的六十分之一秒。

Run Loop 当创建CADisplayLink的时候,我们需要指定一个run loop和run loop mode,对于run loop来说,我们就使用了主线程的run loop,因为任何用户界面的更新都需要在主线程执行,但是模式的选择就并不那么清楚了,每个添加到run loop的任务都有一个指定了优先级的模式,为了保证用户界面保持平滑,iOS会提供和用户界面相关任务的优先级,而且当UI很活跃的时候的确会暂停一些别的任务。

#####物理模拟


参考资料:https://zsisme.gitbooks.io/ios-/content/index.html

转载于:https://my.oschina.net/asjoker/blog/788244

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值