是一种让计时功能在Swift中的多个动画上起作用的方法吗?

问题描述

我正在尝试重新创建Apple在其活动应用中使用的活动环。这是那些不知道的图片

Apple Progress Ring.

我在重建它方面做得不错,我特别努力的是重叠的阴影。最后,我使用的解决方法是将戒指分成两部分,前75%和后25%,这样我就可以使后25%带有阴影并看起来与自身重叠。

现在,我已经完成了此操作,动画计时变得更加困难。现在,我需要处理三个动画。

  1. 戒指的前75%
  2. 戒指的最后25%
  3. 如果环超过100%,则旋转环

以下是演示此视频的视频。 Streamable Link.

出于说明目的,以下是最后25%的颜色,因此您可以visualise it.

如您所见,动画的时间安排有点混乱。所以我的时间安排如下

  1. 如果戒指的填充率为75%或以下,则需要2.25秒的时间来填充缓解时间功能
  2. 如果戒指的填充量介于75%和100%之间,则前75%的填充时间为1秒,而后25%的填充时间为1.25秒,具有缓动计时功能
  3. 如果环的填充率超过100%,则前75%会花费1秒,后25%会花费1秒,并且环的旋转也需要1秒,并具有缓动计时功能

我的问题是,是否可以链接这些单独的CABasicAnimations,以便我可以将总时间设置为2.25秒,并为该组设置计时功能,以便为每个动画动态计算计时,并且计时功能会影响所有三个?

到目前为止,这是我的代码,它包含3个动画功能

percent =填充戒指的数量

  • gradientMaskPart1 =第一个75%的环形层

  • gradientMaskPart2 =最后25%的环形层

  • containerLayer =图层,可同时容纳两个渐变蒙版部件并旋转以模拟重叠的环。

     private func animateRing() {
    
     let needsMultipleAnimations = percent <= 0.75 ? false : true
    
     CATransaction.begin()
    
     if needsMultipleAnimations { CATransaction.setCompletionBlock(ringEndAnimation) }
    
     let basicAnimation = CABasicAnimation(keyPath: "strokeEnd")
     basicAnimation.fromValue = currentFill
     basicAnimation.tovalue = percent > 0.75 ? 0.75 : percent
     currentFill = Double(percent)
     basicAnimation.fillMode = .forwards
     basicAnimation.isRemovedOnCompletion = false
     basicAnimation.duration = needsMultipleAnimations ? 1 : 2.25
     basicAnimation.timingFunction =
         needsMultipleAnimations ? .none : camediatimingFunction(name: camediatimingFunctionName.eaSEOut)
    
    
     gradientMaskPart1.add(basicAnimation,forKey: "basicAnimation")
    
     CATransaction.commit()
    
     }
    
  •   private func ringEndAnimation() {
    
    let needsMultipleAnimations = percent <= 1 ? false : true

    CATransaction.begin()

    if needsMultipleAnimations { CATransaction.setCompletionBlock(rotateRingAnimation) }

    let duration = needsMultipleAnimations ? 1 : 1.25
    let timingFunction: camediatimingFunction? =
        needsMultipleAnimations ? .none : camediatimingFunction(name: camediatimingFunctionName.eaSEOut)

    let basicAnimation = CABasicAnimation(keyPath: "strokeEnd")
    basicAnimation.fromValue = 0
    basicAnimation.tovalue = percent <= 1 ? (percent-0.75)*4 : 1

    basicAnimation.duration = duration
    basicAnimation.fillMode = .forwards
    basicAnimation.isRemovedOnCompletion = false
    basicAnimation.timingFunction = timingFunction


    self.gradientMaskPart2.isHidden = false
    self.gradientMaskPart2.add(basicAnimation2,forKey: "basicAnimation")

    CATransaction.commit()
    }
  •   private func rotateRingAnimation() {
    
    let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
    rotationAnimation.fromValue = 2*CGFloat.pi
    rotationAnimation.tovalue = 2*CGFloat.pi+((2*(percent-1)*CGFloat.pi))
    rotationAnimation.duration = 1
    rotationAnimation.fillMode = camediatimingFillMode.forwards
    rotationAnimation.isRemovedOnCompletion = false
    rotationAnimation.timingFunction = camediatimingFunction(name: camediatimingFunctionName.eaSEOut)
    self.containerLayer.add(rotationAnimation,forKey: "rotation")
    }

解决方法

此解决方案演示了如何使用CABasicAnimation与专用的CAAnimationGroup同步制作多层动画。

  1. 为应该同步设置动画的图层创建一个容器CALayer子类。我们称之为TestLayer
  2. 为需要设置动画的每个图层定义属性。
  3. 当动画通过needsDisplay(forKey:)修改特定图层时,在TestLayer中覆盖display以接收keyPath调用。
  4. init(layer:)中覆盖TestLayer,将子层分配给testLayer.presentation()层,该层呈现TestLayer的动画。

这是托管TestView的{​​{1}}的全部代码。

TestLayer

-

class TestLayer: CALayer {
    @NSManaged @objc dynamic var layer1: CAGradientLayer
    @NSManaged @objc dynamic var layer2: CAGradientLayer
    
    override init() {
        super.init()
        let gradientLayer1 = CAGradientLayer()
        let gradientLayer2 = CAGradientLayer()
        gradientLayer1.colors = [UIColor.red.cgColor,UIColor.green.cgColor]
        gradientLayer2.colors = [UIColor.green.cgColor,UIColor.red.cgColor]
        gradientLayer1.startPoint = CGPoint(x: 0.5,y: 0.5)
        gradientLayer1.endPoint = CGPoint(x: 1.0,y: 0.5)
        gradientLayer2.startPoint = CGPoint(x: 0,y: 0.5)
        gradientLayer2.endPoint = CGPoint(x: 0.5,y: 0.5)
        addSublayer(gradientLayer1)
        addSublayer(gradientLayer2)
        layer1 = gradientLayer1
        layer2 = gradientLayer2
    }
    
    override init(layer: Any) {
        super.init(layer: layer)
        if let testLayer = layer as? TestLayer {
            layer1 = testLayer.layer1
            layer2 = testLayer.layer2
        }
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func layoutSublayers() {
        var bounds1 = self.bounds
        bounds1.size.height /= 2.0
        var bounds2 = bounds1
        bounds2.origin.y = bounds1.maxY
        super.layoutSublayers()
        layer1.frame = bounds1
        layer2.frame = bounds2
    }
    
    override class func needsDisplay(forKey key: String) -> Bool {
        if key == "layer1" || key == "layer2" {
            return true
        }
        return super.needsDisplay(forKey: key)
    }
}

如果要测试它,请创建一个虚拟视图控制器,并像这样覆盖其class TestView: UIView { override class var layerClass: AnyClass { get { return TestLayer.self } } var testLayer: TestLayer { get { return layer as! TestLayer } } override init(frame: CGRect) { super.init(frame: frame) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func startAnimation() { let animLayer1 = CABasicAnimation(keyPath: "layer1.startPoint") animLayer1.fromValue = CGPoint(x: 0.5,y: 0.5) animLayer1.toValue = CGPoint(x: 0.0,y: 0.5) animLayer1.duration = 1.0 animLayer1.autoreverses = true let animLayer2 = CABasicAnimation(keyPath: "layer2.endPoint") animLayer2.fromValue = CGPoint(x: 0.5,y: 0.5) animLayer2.toValue = CGPoint(x: 1.0,y: 0.5) animLayer2.duration = 1.0 animLayer2.autoreverses = true let group = CAAnimationGroup() group.duration = 1.0 group.autoreverses = true group.repeatCount = Float.greatestFiniteMagnitude group.animations = [animLayer1,animLayer2] testLayer.add(group,forKey: "TestAnim") } }

viewWillAppear

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...