ios - CALayer (CAAnimation) 动画属性绘制错误
问题描述
我正在创建带有条形动画的条形图。我将它们绘制在视图下划线层(这是我的自定义BarChartLayer
)中。这一层负责所有的图纸。
对于动画图表条,我使用名为procentAnimation
. 在这个值的动画变化期间,在 draw 方法中,我计算了两个数组 ( oldValues
, newValues
) 的值之间的步长。
从 oldValues 到 newValues 的第一个动画效果很好。在动画委托中的动画之后,我交换了两个数组的值。
第二个动画应该看起来像反向动画。但是在动画开始后,条形图跳到第一个动画开始位置,然后动画像第一个动画一样连续,完成后图表条形图跳回到第二个动画的最终位置。
在调试模式下,我发现,当我添加第二个动画时,属性的值oldValues
并newValues
保持正确,但是当动画在方法draw(in:)
中开始时,值oldValues
并newValues
跳回到第一个动画期间的值。
这是一个错误还是有一些我不明白的机制?如何解决这个问题?
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()
}
}
}
解决方案
您应该使用标记自定义动画属性,@NSManaged
因为 Core Animation 会创建模型层的副本以进行渲染。@NSManaged
不允许默认值。所以你应该改写defaultValue(forKey:)
。
@NSManaged var procentAnimation : CGFloat
override class func defaultValue(forKey inKey: String) -> Any? {
return inKey == "procentAnimation" ? 0.0 : super.defaultValue(forKey: inKey)
}
您也不应该在 CALayer 实现中使用 UIKit 函数。所以你应该用他们的Core Graphics对应物替换UIGraphicsPushContext
和:UIGraphicsPopContext
override func draw(in ctx: CGContext) {
ctx.saveGState()
...
ctx.restoreGState()
}
推荐阅读
- python - 使用变量字符串创建 python 数据框
- c++ - 我用 Cuda 编写的全局函数只运行最后一个块
- angular - 检查标记坐标是否在范围内
- selenium - Wait.until 不能在 web 项目中的 selenium 中工作
- python - 如何解决 ValueError: Unknown load command: 50 in pyinstaller OSX
- python - 将字符串转换为数据框
- angular - 如何在角度 4 中选择下拉值
- node.js - 在开发服务器上运行带有永久监视器的 NodeJS 进程
- php - 如何使用 array_push 在 PHP 中使用 while 循环将每个值及其键推送到 $row
- javascript - 超时链接方法