在圆形路径上为 CAShapeLayer 设置动画

animate CAShapeLayer on circular path

我想在绘制的路径上为我的拇指图层设置动画。我可以根据值设置进度层的动画,但是移动整个层不起作用。

func setProgress(_ value:Double, _ animated :Bool) {
    let progressAnimation = CABasicAnimation(keyPath: "strokeEnd")
    progressAnimation.duration = animated ? 0.6 : 0.0
    progressAnimation.fromValue = currentValue
    progressAnimation.toValue = value
    progressAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
    progressLayer.strokeEnd = CGFloat(value)
    progressLayer.add(progressAnimation, forKey: "animateprogress")
}

使用这个函数我可以为进度层设置动画。我一直坚持动画拇指位置。

我尝试了一些方法,但没有用。

失败尝试 1

    guard let path = thumbLayer.path else {return}
    let center = CGPoint(x: frame.width/2, y: frame.height/2)
    let radius = min(frame.width, frame.height)/2 - max(trackWidth, max(progressWidth, thumbWidth))/2
    let thumbAngle = 2 * .pi * currentValue - (.pi/2)
    let thumbX = CGFloat(cos(thumbAngle)) * radius
    let thumbY = CGFloat(sin(thumbAngle)) * radius
    let newThumbCenter = CGPoint(x: center.x + thumbX, y: center.y + thumbY)
    let thumbPath = UIBezierPath(arcCenter: newThumbCenter, radius: thumbWidth/2, startAngle: -.pi/2, endAngle: .pi*1.5, clockwise: true)

    let thumbAnimation = CABasicAnimation(keyPath: "path")
    thumbAnimation.duration = animated ? 0.6 : 0.0
    thumbAnimation.fromValue = path
    thumbAnimation.toValue = thumbPath.cgPath
    thumbAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
    thumbLayer.strokeEnd = CGFloat(value)
    thumbLayer.add(thumbAnimation, forKey: "animateprogress")

失败的尝试 2

    let intialPosition = thumbLayer.position
    let center = CGPoint(x: frame.width/2, y: frame.height/2)
    let radius = min(frame.width, frame.height)/2 - max(trackWidth, max(progressWidth, thumbWidth))/2
    let thumbAngle = 2 * .pi * currentValue - (.pi/2)
    let thumbX = CGFloat(cos(thumbAngle)) * radius
    let thumbY = CGFloat(sin(thumbAngle)) * radius
    let newThumbCenter = CGPoint(x: center.x + thumbX - thumbCenter.x, y: center.y + thumbY - thumbCenter.y)

    thumbLayer.position.x = newThumbCenter.x
    let thumbAnimationX = CABasicAnimation(keyPath: "progress.x")
    thumbAnimationX.duration = animated ? 0.6 : 0.0
    thumbAnimationX.fromValue = intialPosition.x
    thumbAnimationX.toValue = newThumbCenter.x
    thumbAnimationX.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
    thumbLayer.add(thumbAnimationX, forKey: "progress.x")


    thumbLayer.position.y = newThumbCenter.y
    let thumbAnimationY = CABasicAnimation(keyPath: "progress.y")
    thumbAnimationY.duration = animated ? 0.6 : 0.0
    thumbAnimationY.fromValue = intialPosition.y
    thumbAnimationY.toValue = newThumbCenter.y
    thumbAnimationY.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
    thumbLayer.add(thumbAnimationY, forKey: "progress.y")

在您的第一次尝试中,您尝试为拇指的路径(红色圆圈的轮廓)设置动画。在您的第二次尝试中,您试图为 thumbLayerposition 设置动画(但您为错误的关键路径设置了动画)。

由于您在执行其中任何一项时遇到困难,让我们尝试一种不同的方法。看看标准 Swiss railway clock:

具体看秒针末端的红点。时钟如何在脸上移动那个红点?通过将秒针绕着脸的中心旋转。

这就是我们要做的,但我们不会绘制整个秒针。我们只在最后画红点。

我们将 thumbLayer 坐标系的原点放在圆形轨道的中心,我们将设置它的路径,使拇指出现在路径上。这是布局:

这是设置 circleLayerthumbLayer 的代码:

    private let circleLayer = CAShapeLayer()
    private let thumbLayer = CAShapeLayer()

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()

        let circleRadius: CGFloat = 100
        let thumbRadius: CGFloat = 22
        let circleCenter = CGPoint(x: view.bounds.midX, y: view.bounds.midY)

        circleLayer.fillColor = nil
        circleLayer.strokeColor = UIColor.black.cgColor
        circleLayer.lineWidth = 4
        circleLayer.lineJoin = .round
        circleLayer.path = CGPath(ellipseIn: CGRect(x: -circleRadius, y: -circleRadius, width: 2 * circleRadius, height: 2 * circleRadius), transform: nil)
        circleLayer.position = circleCenter
        view.layer.addSublayer(circleLayer)

        thumbLayer.fillColor = UIColor.red.cgColor
        thumbLayer.strokeColor = nil
        thumbLayer.path = CGPath(ellipseIn: CGRect(x: -thumbRadius, y: -circleRadius - thumbRadius, width: 2 * thumbRadius, height: 2 * thumbRadius), transform: nil)
        thumbLayer.position = circleCenter
        view.layer.addSublayer(thumbLayer)
    }

通过这种布局,我们可以在不改变位置的情况下沿着轨道移动拇指。相反,我们围绕原点旋转 thumbLayer

    @IBAction func buttonWasTapped() {
        let animation = CABasicAnimation(keyPath: "transform.rotation")
        animation.fromValue = 0
        animation.toValue = 2 * CGFloat.pi
        animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
        animation.duration = 1.5
        animation.isAdditive = true
        thumbLayer.add(animation, forKey: nil)
    }

结果: