代码之家  ›  专栏  ›  技术社区  ›  Yuriy Fedchenko

CALayer(CAAnimation)可设置动画的属性绘图错误

  •  0
  • Yuriy Fedchenko  · 技术社区  · 5 年前

    我正在创建条形图,带有条形动画。我在视图下划线层(这是我的习惯)中绘制它们 BarChartLayer ). 该层负责所有图纸。

    我使用名为 procentAnimation . 在这个值的动态变化过程中,在draw方法中,我计算两个数组的值之间的步长( oldValues newValues ).

    从oldValues到newValues的第一个动画效果很好。在动画代理中设置动画后,我交换两个数组的值。

    第二个动画应该看起来像反转动画。但在动画开始后,条形图跳到第一个动画开始位置,然后像第一个动画一样连续动画,完成后,条形图跳回到第二个动画的最终位置。

    在调试模式下,当我添加第二个动画时,我发现属性的值 旧值 新值 保持正确,但当动画在方法中开始时 draw(in:) 价值观 新值 跳回到第一个动画中的场景。

    class BarChartLayer: CALayer {
    
        var oldValues: [CGFloat] = [45, 34, 12, 88, 17, 77, 88, 10] {
            didSet {
                debugPrint("oldValues set")
            }
        }
        var newValues: [CGFloat] = [92, 72, 64, 32, 28, 15, 11, 33] {
            didSet {
                debugPrint("newValues set")
            }
        }
    
        var numberOfBars: CGFloat = 8
        var space: CGFloat = 10
    
        @objc var procentAnimation : CGFloat = 0
        override class func needsDisplay(forKey key: String) -> Bool {
            if key == #keyPath(procentAnimation) {
                return true
            }
            return super.needsDisplay(forKey:key)
        }
    
        func animateBarsChange() {
            let ba = CABasicAnimation(keyPath:#keyPath(BarChartLayer.procentAnimation))
            procentAnimation = 100
            ba.duration = 4.0
            ba.fromValue = 0
            ba.delegate = self
            add(ba, forKey:nil)
        }
    
        func barWidth(rect: CGRect) -> CGFloat {
            return (rect.width - CGFloat(space * (numberOfBars - 1))) / CGFloat(numberOfBars)
        }
    
        func procentRanndomizer(rect: CGRect) -> CGFloat {
            return CGFloat.random(in: 1...100) * rect.height / 100
        }
    
        func makeProcentFor(value: CGFloat, rect: CGRect) -> CGFloat {
            return value * rect.height / 100
        }
    
        func additionalHeight(index: Int) -> CGFloat {
            return (newValues[index] - oldValues[index]) / 100
        }
    
        func makeRandomArr() -> [CGFloat] {
            var arr = [CGFloat]()
            for _ in 0..<8 {
                arr.append(CGFloat.random(in: 1...100))
            }
            return arr
        }
    
        override func draw(in ctx: CGContext) {
            UIGraphicsPushContext(ctx)
            let con = ctx
            let rect = self.bounds
    
            con.addRect(rect)
            con.setFillColor(UIColor.gray.cgColor)
            con.fillPath()
    
            var px:CGFloat = 0
            let bw = barWidth(rect: rect)
    
            debugPrint("OLD - \(oldValues)")
            debugPrint("NEW - \(newValues)")
    
            let oldValues = self.oldValues
    
            for i in 0..<Int(numberOfBars) {
                px = CGFloat(i) * (bw + space)
                let h = oldValues[i] + procentAnimation * additionalHeight(index: i)
                con.addRect(CGRect(x: px, y: 0, width: bw, height: makeProcentFor(value: h, rect: rect)))
            }
    
            con.setFillColor(UIColor.green.cgColor)
            con.fillPath()
            UIGraphicsPopContext()
        }
    }
    
    extension BarChartLayer: CAAnimationDelegate {
        func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
            if flag {
                let temp = self.oldValues
                self.oldValues = self.newValues
                self.newValues = temp//makeRandomArr()
                self.procentAnimation = 0
                debugPrint("------------------------------------")
                setNeedsDisplay()
            }
        }
    }
    
    0 回复  |  直到 5 年前
        1
  •  0
  •   clemens    5 年前

    应使用标记自定义可设置动画的属性 @NSManaged 因为核心动画会创建模型层的副本以进行渲染。 @NSM管理 不允许默认值。所以你应该重写 defaultValue(forKey:) 相反。

    @NSManaged var procentAnimation : CGFloat
    
    override class func defaultValue(forKey inKey: String) -> Any? {
        return inKey == "procentAnimation" ? 0.0 : super.defaultValue(forKey: inKey)
    }
    

    UIGraphicsPushContext UIGraphicsPopContext 与核心图形对应:

    override func draw(in ctx: CGContext) {
        ctx.saveGState()
        ...
        ctx.restoreGState()
    }