如何在iOS中设置自定义属性的动画
我有一个自定义UIView,它使用核心图形调用绘制其内容。所有这些都工作正常,但现在我想设置一个影响显示的值变化的动画。我有一个自定义属性可以在自定义UView中实现这一点:如何在iOS中设置自定义属性的动画,ios,swift,uiviewanimation,Ios,Swift,Uiviewanimation,我有一个自定义UIView,它使用核心图形调用绘制其内容。所有这些都工作正常,但现在我想设置一个影响显示的值变化的动画。我有一个自定义属性可以在自定义UView中实现这一点: var _anime: CGFloat = 0 var anime: CGFloat { set { _anime = newValue for(gauge) in gauges { gauge.animate(newValue) }
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,它将正常工作
我来自安卓系统的背景,所以整个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
如果在
gauge.animate(newValue)
函数中修改任何自动布局约束,则需要调用layoufifreeded()
而不是setNeedsDisplay()
简短回答:使用
CADisplayLink
每n帧呼叫一次。示例代码:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let displayLink = CADisplayLink(target: self, selector: #selector(animationDidUpdate))
displayLink.preferredFramesPerSecond = 50
displayLink.add(to: .main, forMode: .defaultRunLoopMode)
updateValues()
}
var animationComplete = false
var lastUpdateTime = CACurrentMediaTime()
func updateValues() {
self.emaxView.animate(0);
lastUpdateTime = CACurrentMediaTime()
animationComplete = false
}
func animationDidUpdate(displayLink: CADisplayLink) {
if(!animationComplete) {
let now = CACurrentMediaTime()
let interval = (CACurrentMediaTime() - lastUpdateTime)/animationDuration
self.emaxView.animate(min(CGFloat(interval), 1))
animationComplete = interval >= 1.0
}
}
}
代码可以被细化和概括,但它正在做我需要的工作。如果完全用CoreGraphics绘制,如果你想做一点数学,有一种非常简单的方法来制作动画。幸运的是,这里有一个刻度,告诉你旋转的弧度数,所以数学是最小的,不涉及三角。这样做的好处是,您不必重新绘制整个背景,甚至不必重新绘制指针。这可能有点棘手,以获得正确的角度和东西,我可以帮助,如果以下不工作 通常在Draw(矩形)中绘制视图的背景。你应该把指针放进CALayer里。您几乎可以将指针的绘图代码(包括中间的深灰色圆圈)移动到一个单独的方法中,该方法返回UIImage。图层将根据视图的框架(在布局子视图中)调整大小,并且必须将定位点设置为(0.5,0.5),这实际上是默认值,因此您可以忽略该线。然后,动画方法只是根据需要更改层的变换以旋转。我会这样做的。我要更改方法和变量名,因为anime和animate太模糊了 因为图层特性以0.25的持续时间隐式设置动画,所以您甚至不需要调用动画方法就可以摆脱此问题。我已经有一段时间没有和CoreAnimation合作了,所以很明显要测试一下 这里的优点是,您只需将刻度盘的转速设置为所需的转速,它就会旋转到该转速。没有人会读你的代码,并像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提高到所需的值时,它都会旋转到该值
编辑:测试它,它工作。除了我犯了几个错误-您不需要更改层的位置,我相应地更新了代码。此外,更改图层的变换会触发直接父视图中的LayoutSubview。我忘了这件事。最简单的方法是将指针层放入UIView,UIView是SpeedDial的子视图。我已经更新了代码。祝你好运这可能有些过分,但它比设置视图、背景和所有对象的整个渲染动画更具可重用性。您试图通过此属性更改实现什么?如果该属性更改了在draw(in:rect)中执行的绘图的某些方面,您将无法将其设置为那样的动画。@twiz_uu似乎是这样。还有其他方法的建议吗?看看如何使用
CAAnimation
修改自定义draw rect图形!我不确定UIView.animate
way是否适用于自定义绘制rect图形@克莱德,如果我知道你想制作什么动画,我真的会的。您是否有前后图片或一般描述?很可能最终,您将不得不将动画方面放在CALayer中,并为层设置动画。另外,func animate
的代码是helpful@twiz_我更新了更详细的问题布局没有被修改setNeedsDisplay()
触发对draw()
的调用,这是更新显示所需的全部内容。感谢您的详细回答。是的,这一切都是通过核心图形完成的,CGContext被缩放和转换,这样我就可以在一个以针为中心的100x100框中绘制。如何设置数值的动画?这需要重新绘制每个动画周期。此外,在同一个UIView中有多个类似的仪表,所有仪表都会同时更新,因此将它们一起设置动画是有意义的。添加多个子视图会变得混乱-现在有一个类来绘制仪表,它非常轻量级,每个仪表有一个参数化实例。我还有其他需要动画的元素,这是标准UIView或图层属性无法完成的-例如,改变长度和颜色的条形图。不用担心。改变长度和颜色的条最好作为层设置动画。更改RPM文本-我只需将UILabel子视图添加到快速拨号盘,并相应地定位,然后更改RPM变量的didSet子句中的值