iOS CAMediaTiming协议理解

CAMediaTiming协议

Methods that model a hierarchical timing system, allowing objects to map time between their parent and local time.

为分级计时系统建模的方法,允许对象在它们的父时间和本地时间之间映射时间。

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

该协议中定义了很多非常有用的属性:

duration

duration是一个CFTimeInterval的类型(类似于NSTimeInterval的一种双精度浮点类型),指定了动画执行一次的时间。单位秒。默认值为0,实际上动画会执行0.25秒。

repeatCount

repeatCount代表动画重复的执行次数。将此属性设置为.greatestFiniteMagnitude将导致动画永远重复。默认值为0,实际上动画会执行1次。

比如下面小车移动的代码,设置了duration和repeatCount都为0,但是动画还是执行了一次,时长为0.25秒。

class ViewController: UIViewController {
    
    var path = UIBezierPath()
    var pathShape = CAShapeLayer()
    var carShape = CALayer()

    override func viewDidLoad() {
        super.viewDidLoad()
        drawAnimation()
    }

    func drawAnimation() {
        path.move(to: CGPoint(x: 40, y: 400))
        path.addCurve(to: CGPoint(x: 400, y: 400), controlPoint1: CGPoint(x: 150, y: 200), controlPoint2: CGPoint(x: 300, y: 500))
        
        pathShape.path = path.cgPath
        pathShape.strokeColor = UIColor.red.cgColor
        pathShape.fillColor = nil
        self.view.layer.addSublayer(pathShape)
        
        carShape.frame = CGRect(x: 25, y: 385, width: 30, height: 30)
        carShape.contents = UIImage(named: "car")?.cgImage
        carShape.anchorPoint = CGPoint(x: 0.5, y: 0.8)
        self.view.layer.addSublayer(carShape)
    }
    
    @IBAction func startAnimation(_ sender: UIButton) {
        let moveAnamation = CAKeyframeAnimation(keyPath: "position")
        moveAnamation.path = path.cgPath
        moveAnamation.duration = 0
        moveAnamation.repeatCount = 0
        moveAnamation.rotationMode = .rotateAuto
        moveAnamation.isRemovedOnCompletion = false
        moveAnamation.fillMode = .forwards
        carShape.add(moveAnamation, forKey: nil)
    }
}

repeatDuration

repeatDuration可以使动画重复一个指定的时间,而不是指定次数。

注意 repeatCountrepeatDuration 可能会相互冲突,所以你只要对其中一个指定非零值。对两个属性都设置非0值的行为没有被定义。

speed

speed 是一个时间的倍数,默认1.0,减少它会减慢图层/动画的时间,增加它会加快速度。如果2.0的速度,那么对于一个duration为1的动画,实际上在0.5秒的时候就已经完成了。

beginTime

beginTime 指定了动画开始之前的的延迟时间。这里的延迟从动画添加到可见图层的那一刻开始测量,默认是0(就是说动画会立刻执行)。

特别要注意的是:beginTime是相对于其父对象的开始时间,

如果将动画直接添加到CAAnimationGroup中,那么beginTime则是基于CAAnimationGroupbeginTime的偏移时间。

如果将动画直接添加到layer上,那么beginTime则是基于layer添加到父layer的时间的偏移时间(很有可能是一个过去的时间),这个时候设置动画的beginTime,执行动画的时候很有可能动画直接就到结束的那一帧了。

比如上面的那段代码中,小汽车的layer在viewDidLoad中就添加到layer里面了,而执行动画需要点击start按钮,现在将start方法改一下:

    @IBAction func startAnimation(_ sender: UIButton) {
        let moveAnamation = CAKeyframeAnimation(keyPath: "position")
        moveAnamation.path = path.cgPath
        moveAnamation.duration = 5
        moveAnamation.beginTime = 5
        moveAnamation.rotationMode = .rotateAuto
        moveAnamation.isRemovedOnCompletion = false
        moveAnamation.fillMode = .forwards
        carShape.add(moveAnamation, forKey: nil)
    }

代码中,动画执行5秒,并且5秒之后执行,但是实际运行结果则是动画开始就到了结束的最后一帧。原因如上面所说的,延迟5秒后的时间估计已经是过去的时间了,而不是未来的时间,所以动画直接就结束了。

那么该怎么办呢?这里需要加上父对象的时间,此时需要下面的方法:

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

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

需改一下动画开始的方法

    @IBAction func startAnimation(_ sender: UIButton) {
        let currentTimeInSuperView = self.view.layer.convertTime(CACurrentMediaTime(), from: nil)
        let addTime = self.carShape.convertTime(currentTimeInSuperView, from: self.view.layer)
        
        let moveAnamation = CAKeyframeAnimation(keyPath: "position")
        moveAnamation.path = path.cgPath
        moveAnamation.duration = 5
        moveAnamation.beginTime = addTime + 5
        moveAnamation.rotationMode = .rotateAuto
        moveAnamation.isRemovedOnCompletion = false
        moveAnamation.fillMode = .forwards
        carShape.add(moveAnamation, forKey: nil)
    }

修改完动画的执行就达到了我们想要的效果。

timeOffset

timeOffsetbeginTime类似,但是和增加beginTime导致的延迟动画不同,增加 timeOffset只是让动画快进到某一点,从这点开始执行动画,例如,对于一个持续1秒的动画来说,设置 timeOffset 为0.5意味着动画将从一半的地方开始,执行一个周期。

timeOffset并不受speed的影响。所以当把speed设为2,timeOffset为2.5,duration为5秒,那么实际上动画将会从一半的位置开始,执行2.5秒结束。

也就是说动画开始执行点是由timeOffset和duration决定的,而动画实际执行时间则是speed和duration决定的。

    @IBAction func startAnimation(_ sender: UIButton) {
        let moveAnamation = CAKeyframeAnimation(keyPath: "position")
        moveAnamation.path = path.cgPath
        moveAnamation.duration = 5
        moveAnamation.speed = 2
        moveAnamation.timeOffset = 2.5
        moveAnamation.rotationMode = .rotateAuto
        moveAnamation.isRemovedOnCompletion = false
        moveAnamation.fillMode = .forwards
        carShape.add(moveAnamation, forKey: nil)
    }

 

fillMode

filleMode: 填充模式,可以理解为动画时间上的一种填充,比如动画开始之前和动画结束之后的显示内容。可选值有如下几种:

  • removed:默认值,动画开始前和结束后,动画对图层都没有影响,图层依然保持初始值。
  • forwards:动画结束后保持最终的可见状态。
  • backwards:动画开始前,只要加入动画,图层就会处于动画的初始状态,即第一帧动画。
  • both:综合了forwards和backwards两种功能,动画加入图层到真正执行动画的时间段里,图层保持动画初始状态;动画结束之后保持动画最终状态。

来一段代码具体描述一下:

    func fillModeDemo() {
        let colorLayer = CALayer()
        colorLayer.position = self.view.layer.position
        colorLayer.bounds = CGRect(x: 0, y: 0, width: 50, height: 50)
        colorLayer.backgroundColor = UIColor.orange.cgColor
        self.view.layer.addSublayer(colorLayer)
        
        let basicAnimation = CABasicAnimation(keyPath: "bounds")
        basicAnimation.fromValue = CGRect(x: 0, y: 0, width: 200, height: 200)
        basicAnimation.toValue = CGRect(x: 0, y: 0, width: 400, height: 400)
        basicAnimation.beginTime = 3
        basicAnimation.duration = 4
        basicAnimation.fillMode = .removed
//        basicAnimation.fillMode = .forwards
//        basicAnimation.fillMode = .backwards
//        basicAnimation.fillMode = .both
        
        let animationGroup = CAAnimationGroup()
        animationGroup.duration = 10
        animationGroup.animations = [basicAnimation]
        colorLayer.add(animationGroup, forKey: nil)
    }

上面代码中,调用后,添加一个50*50的颜色图层到父视图中,然后创建一个变大的动画,宽高由200变到400,延迟3秒执行,执行4秒动画,并分别设置fillMode属性,然后矿建动画组,执行时间10秒,将变大动画添加到动画组,并最后把动画组添加到颜色图层中。

为了记录动画执行了10秒,界面上加了一个计时器,通过上面的代码可以判断动画执行过程:动画组开始->等待3秒->执行放大动画4秒->等待3秒->动画组结束。

下面看一下4个枚举值的不同效果:

          

效果分析:

  • removed:动画开始时,colorLayer尺寸(50,50),动画组执行到3秒时,colorLayer尺寸突然变成(200,200),然后经过4秒动画变成(400,400),当到第7秒的时候,colorLayer尺寸突然变回(50,50),然后动画组继续执行3秒,结束。
  • forwards:动画开始时,colorLayer尺寸(50,50),动画组执行到3秒时,colorLayer尺寸突然变成(200,200),然后经过4秒动画变成(400,400),此后colorLayer尺寸一直保持在(400,400),直到结束。
  • backwards:动画开始时,colorLayer尺寸(50,50)突然变成(200,200),第3秒时,colorLayer经过4秒动画由(200,200)变成(400,400),当到第7秒的时候,colorLayer尺寸突然变回(50,50),然后动画组继续执行3秒,结束。
  • both:综合了forwards和backwards两种功能。

参考文档:Apple CAMediaTiming

文中如有不正确的地方,还请路过的朋友指正。

本篇文章出自https://blog.csdn.net/guoyongming925的博客,如需转载,请标明出处。

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值