我有一个自定义UIView,它使用Core Graphics调用来绘制其内容。一切运作良好,但现在我想动画一个影响显示的价值变化。我有一个自定义属性来实现我的自定义UView:
var _anime: CGFloat = 0
var anime: CGFloat {
set {
_anime = newValue
for(gauge) in gauges {
gauge.animate(newValue)
}
setNeedsDisplay()
}
get {
return _anime
}
}
我已经从ViewController启动了一个动画:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.emaxView.anime = 0.5
UIView.animate(withDuration: 4) {
DDLogDebug("in animations")
self.emaxView.anime = 1.0
}
}
这不起作用 - 动画值确实从0.5变为1.0,但它立即生效。有两个动画设置器的调用,一个值为0.5,然后立即调用1.0。如果我更改属性,我将动画设置为标准的UIView属性,例如alpha,它工作正常。
我来自Android背景,因此整个iOS动画框架看起来像我的黑魔法。除了预定义的UIView属性之外,还有什么方法可以动画属性吗?
下面是动画视图应该是什么样子 - 它每1/2秒获得一个新值,我希望指针在此时间内从前一个值平滑移动到下一个值。更新它的代码是:
open func animate(_ progress: CGFloat) {
//DDLogDebug("in animate: progress \(progress)")
if(dataValid) {
currentValue = targetValue * progress + initialValue * (1 - progress)
}
}
在更新后调用draw()
会使其重新绘制新的指针位置,在initialValue
和targetValue
答案 0 :(得分:1)
简答:使用AppDelegate
每n帧调用一次。示例代码:
CADisplayLink
}
代码可以改进和推广,但它正在完成我需要的工作。
答案 1 :(得分:0)
如果完全使用CoreGraphics绘制,如果你想做一些数学运算,有一种非常简单的动画制作方法。幸运的是,你有一个刻度可以告诉你完全旋转的弧度数,所以数学是最小的,不涉及三角学。这样做的好处是你不必重绘整个背景,甚至指针。如果以下方法不起作用,我可以提供帮助。
通常在绘图中绘制视图的背景(在rect中)。你应该把它放到CALayer中的指针。您几乎可以只将指针的绘制代码移动到一个返回UIImage的单独方法中,包括中心深灰色圆圈。图层的大小将调整到视图的框架(在布局子视图中),并且锚点必须设置为(0.5,0.5),这实际上是默认值,因此您应该可以将该行留出。然后,您的动画方法只是根据您的需要更改图层的变换以进行旋转。这是我将如何做到的。我要更改方法和变量名称,因为动画和动画只是有点太模糊了。
因为图层属性隐含地以0.25的持续时间进行动画制作,所以即使不调用动画方法也可以离开。自从我使用CoreAnimation以来已经有一段时间了,所以显然要测试它。
这里的优点是你只需将表盘的RPM设置为你想要的,它就会旋转到那个速度。没有人会读你的代码,就像WTF是_anime! :)我已经包含了init方法来提醒你改变图层的内容比例(或者它呈现的是低质量),显然你的init中可能还有其他东西。
class SpeedDial: UIView {
var pointer: CALayer!
var pointerView: UIView!
var rpm: CGFloat = 0 {
didSet {
pointer.setAffineTransform(rpm == 0 ? .identity : CGAffineTransform(rotationAngle: rpm/25 * .pi))
}
}
override init(frame: CGRect) {
super.init(frame: frame)
pointer = CALayer()
pointer.contentsScale = UIScreen.main.scale
pointerView = UIView()
addSubview(pointerView)
pointerView.layer.addSublayer(pointer)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
pointer = CALayer()
pointer.contentsScale = UIScreen.main.scale
pointerView = UIView()
addSubview(pointerView)
pointerView.layer.addSublayer(pointer)
}
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
context.saveGState()
//draw background with values
//but not the pointer or centre circle
context.restoreGState()
}
override func layoutSubviews() {
super.layoutSubviews()
pointerView.frame = bounds
pointer.frame = bounds
pointer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
pointer.contents = drawPointer(in: bounds)?.cgImage
}
func drawPointer(in rect: CGRect) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0)
guard let context = UIGraphicsGetCurrentContext() else { return nil }
context.saveGState()
// draw the pointer Image. Make sure to draw it pointing at zero. ie at 8 o'clock
// I'm not sure what your drawing code looks like, but if the pointer is pointing
// vertically(at 12 o'clock), you can get it pointing at zero by rotating the actual draw context like so:
// perform this context rotation before actually drawing the pointer
context.translateBy(x: rect.width/2, y: rect.height/2)
context.rotate(by: -17.5/25 * .pi) // the angle judging by the dial - remember .pi is 180 degrees
context.translateBy(x: -rect.width/2, y: -rect.height/2)
context.restoreGState()
let pointerImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return pointerImage
}
}
指针的标识转换使其指向0 RPM,因此每次将RPM提升到所需的值时,它都会向上旋转到该值。
编辑:测试它,它的工作原理。除了我犯了几个错误 - 你不需要更改图层位置,我相应地更新了代码。此外,更改图层的变换会触发直接父级中的layoutSubviews。我忘了这件事。最简单的方法是将指针层放入一个UIView,它是SpeedDial的子视图。我已经更新了代码。祝好运!也许这有点过分,但它比动画整个视图,背景和所有渲染更具可重用性。
答案 2 :(得分:-1)
如果修改layoufIfNeeded()
功能中的任何自动布局限制,则需要致电setNeedsDisplay()
而不是gauge.animate(newValue)
。