通过按钮平滑地动画圆形进度圈
Animating a circular progress circle smoothly via buttons
我的进度圈出现奇怪的视觉怪癖。我希望绿色圆圈 (CAShapeLayer) 以五度为增量平滑地制作动画。 0/5 = 没有绿色圆圈。 5/5 = 完整的绿色圆圈。问题是绿色圆圈在它应该在的地方动画,然后突然缩小回到正确的位置。当同时按下加号和减号按钮时会发生这种情况。下面有一个 gif 演示正在发生的事情。
每个动画的持续时间应该持续 0.25 秒,并且应该从动画最后结束的地方平滑地 向上和向下(取决于是否按下加号或减号按钮)设置动画(目前没有发生。)
在我的 UIView 中,我绘制了圆圈,以及为进度位置设置动画的方法:
class CircleView: UIView {
let progressCircle = CAShapeLayer()
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
override func draw(_ rect: CGRect) {
let circlePath = UIBezierPath(ovalIn: self.bounds)
progressCircle.path = circlePath.cgPath
progressCircle.strokeColor = UIColor.green.cgColor
progressCircle.fillColor = UIColor.red.cgColor
progressCircle.lineWidth = 10.0
// Add the circle to the view.
self.layer.addSublayer(progressCircle)
}
func animateCircle(circleToValue: CGFloat) {
let fifths:CGFloat = circleToValue / 5
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.duration = 0.25
animation.fromValue = fifths
animation.byValue = fifths
animation.fillMode = kCAFillModeBoth
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
progressCircle.strokeEnd = fifths
// Create the animation.
progressCircle.add(animation, forKey: "strokeEnd")
}
}
在我的 VC:
我用以下方式初始化圆圈和起点:
let myDrawnCircle = CircleView()
var startingPointForCircle = CGFloat()
viewDidAppear:
startingPointForCircle = 0.0
myDrawnCircle.frame = CGRect(x: 100, y: 100, width: 150, height: 150)
myDrawnCircle.backgroundColor = UIColor.white
myDrawnCircle.animateCircle(circleToValue: startingPointForCircle)
self.view.addSubview(myDrawnCircle)
textLabel.text = "\(startingPointForCircle)"
以及执行炫酷动画的实际按钮:
@IBAction func minusButtonPressed(_ sender: UIButton) {
if startingPointForCircle > 0 {
startingPointForCircle -= 1
textLabel.text = "\(startingPointForCircle)"
myDrawnCircle.animateCircle(circleToValue: startingPointForCircle)
}
}
@IBAction func plusButtonPressed(_ sender: UIButton) {
if startingPointForCircle < 5 {
startingPointForCircle += 1
textLabel.text = "\(startingPointForCircle)"
myDrawnCircle.animateCircle(circleToValue: startingPointForCircle)
}
}
这是一张 gif,向您展示了抖动动画的情况。
如何让我的动画从正确的值平滑地动画到正确的值?
我猜这与使用密钥路径有关。我不知道如何使用关键路径准确修复它,但您可以尝试不同的方法,使用自定义视图,并对传递给用于绘制弧线的方法的值进行动画处理。
以下代码由PaintCode生成。为 arc
传递的值指定圆上绘制圆弧的点。这用于自定义 UIView 的 drawRect
方法。制作自定义视图的 CGFloat 属性 并简单地更改该值,然后在动画代码后对视图调用 setNeedsDisplay
。
func drawCircleProgress(frame frame: CGRect = CGRect(x: 0, y: 0, width: 40, height: 40), arc: CGFloat = 270) {
//// General Declarations
let context = UIGraphicsGetCurrentContext()
//// Color Declarations
let white = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)
let white50 = white.colorWithAlpha(0.5)
//// Oval Drawing
CGContextSaveGState(context)
CGContextTranslateCTM(context, frame.minX + 20, frame.minY + 20)
CGContextRotateCTM(context, -90 * CGFloat(M_PI) / 180)
let ovalRect = CGRect(x: -19.5, y: -19.5, width: 39, height: 39)
let ovalPath = UIBezierPath()
ovalPath.addArcWithCenter(CGPoint(x: ovalRect.midX, y: ovalRect.midY), radius: ovalRect.width / 2, startAngle: 0 * CGFloat(M_PI)/180, endAngle: -arc * CGFloat(M_PI)/180, clockwise: true)
white.setStroke()
ovalPath.lineWidth = 1
ovalPath.stroke()
CGContextRestoreGState(context)
}
想要的结果就像在 animateCircle 方法中注释掉 fromValue / byValue 一样简单。 @dfri 指出了这一点 - "set both fromValue and toValue to nil then: "在目标层表示层中 keyPath 的先前值和目标层表示层中 keyPath 的当前值之间进行插值。
//animation.fromValue = fifths
//animation.byValue = fifths
我的进度圈出现奇怪的视觉怪癖。我希望绿色圆圈 (CAShapeLayer) 以五度为增量平滑地制作动画。 0/5 = 没有绿色圆圈。 5/5 = 完整的绿色圆圈。问题是绿色圆圈在它应该在的地方动画,然后突然缩小回到正确的位置。当同时按下加号和减号按钮时会发生这种情况。下面有一个 gif 演示正在发生的事情。
每个动画的持续时间应该持续 0.25 秒,并且应该从动画最后结束的地方平滑地 向上和向下(取决于是否按下加号或减号按钮)设置动画(目前没有发生。)
在我的 UIView 中,我绘制了圆圈,以及为进度位置设置动画的方法:
class CircleView: UIView {
let progressCircle = CAShapeLayer()
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
override func draw(_ rect: CGRect) {
let circlePath = UIBezierPath(ovalIn: self.bounds)
progressCircle.path = circlePath.cgPath
progressCircle.strokeColor = UIColor.green.cgColor
progressCircle.fillColor = UIColor.red.cgColor
progressCircle.lineWidth = 10.0
// Add the circle to the view.
self.layer.addSublayer(progressCircle)
}
func animateCircle(circleToValue: CGFloat) {
let fifths:CGFloat = circleToValue / 5
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.duration = 0.25
animation.fromValue = fifths
animation.byValue = fifths
animation.fillMode = kCAFillModeBoth
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
progressCircle.strokeEnd = fifths
// Create the animation.
progressCircle.add(animation, forKey: "strokeEnd")
}
}
在我的 VC:
我用以下方式初始化圆圈和起点:
let myDrawnCircle = CircleView()
var startingPointForCircle = CGFloat()
viewDidAppear:
startingPointForCircle = 0.0
myDrawnCircle.frame = CGRect(x: 100, y: 100, width: 150, height: 150)
myDrawnCircle.backgroundColor = UIColor.white
myDrawnCircle.animateCircle(circleToValue: startingPointForCircle)
self.view.addSubview(myDrawnCircle)
textLabel.text = "\(startingPointForCircle)"
以及执行炫酷动画的实际按钮:
@IBAction func minusButtonPressed(_ sender: UIButton) {
if startingPointForCircle > 0 {
startingPointForCircle -= 1
textLabel.text = "\(startingPointForCircle)"
myDrawnCircle.animateCircle(circleToValue: startingPointForCircle)
}
}
@IBAction func plusButtonPressed(_ sender: UIButton) {
if startingPointForCircle < 5 {
startingPointForCircle += 1
textLabel.text = "\(startingPointForCircle)"
myDrawnCircle.animateCircle(circleToValue: startingPointForCircle)
}
}
这是一张 gif,向您展示了抖动动画的情况。
如何让我的动画从正确的值平滑地动画到正确的值?
我猜这与使用密钥路径有关。我不知道如何使用关键路径准确修复它,但您可以尝试不同的方法,使用自定义视图,并对传递给用于绘制弧线的方法的值进行动画处理。
以下代码由PaintCode生成。为 arc
传递的值指定圆上绘制圆弧的点。这用于自定义 UIView 的 drawRect
方法。制作自定义视图的 CGFloat 属性 并简单地更改该值,然后在动画代码后对视图调用 setNeedsDisplay
。
func drawCircleProgress(frame frame: CGRect = CGRect(x: 0, y: 0, width: 40, height: 40), arc: CGFloat = 270) {
//// General Declarations
let context = UIGraphicsGetCurrentContext()
//// Color Declarations
let white = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)
let white50 = white.colorWithAlpha(0.5)
//// Oval Drawing
CGContextSaveGState(context)
CGContextTranslateCTM(context, frame.minX + 20, frame.minY + 20)
CGContextRotateCTM(context, -90 * CGFloat(M_PI) / 180)
let ovalRect = CGRect(x: -19.5, y: -19.5, width: 39, height: 39)
let ovalPath = UIBezierPath()
ovalPath.addArcWithCenter(CGPoint(x: ovalRect.midX, y: ovalRect.midY), radius: ovalRect.width / 2, startAngle: 0 * CGFloat(M_PI)/180, endAngle: -arc * CGFloat(M_PI)/180, clockwise: true)
white.setStroke()
ovalPath.lineWidth = 1
ovalPath.stroke()
CGContextRestoreGState(context)
}
想要的结果就像在 animateCircle 方法中注释掉 fromValue / byValue 一样简单。 @dfri 指出了这一点 - "set both fromValue and toValue to nil then: "在目标层表示层中 keyPath 的先前值和目标层表示层中 keyPath 的当前值之间进行插值。
//animation.fromValue = fifths
//animation.byValue = fifths