通过按钮平滑地动画圆形进度圈

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