Ios 为2个角度之间的UIBezierPath圆弧设置动画

Ios 为2个角度之间的UIBezierPath圆弧设置动画,ios,swift,core-animation,uibezierpath,caanimation,Ios,Swift,Core Animation,Uibezierpath,Caanimation,我已经建立了一个自定义的圆形范围选择器,类似于iOS的闹钟/就寝时间应用程序中的一个。为了实现这一点,我使用了两个ui按钮拇指和一个ui检测识别器以及一个CAShapeLayer+UIBezierPath指示它们之间的时间范围。当我使用触摸输入沿圆移动和调整圆弧本身大小时,效果非常好 请原谅我对圆圈的粗俗描述,以下是选取器工作原理的要点: 问题是我需要使用UISegmentedControl在特定的时间范围预设之间切换,并且我真的希望设置此更改的动画。我已经成功地使用CAKeyframeAni

我已经建立了一个自定义的圆形范围选择器,类似于iOS的闹钟/就寝时间应用程序中的一个。为了实现这一点,我使用了两个
ui按钮
拇指和一个
ui检测识别器
以及一个
CAShapeLayer
+
UIBezierPath
指示它们之间的时间范围。当我使用触摸输入沿圆移动和调整圆弧本身大小时,效果非常好

请原谅我对圆圈的粗俗描述,以下是选取器工作原理的要点:

问题是我需要使用
UISegmentedControl
在特定的时间范围预设之间切换,并且我真的希望设置此更改的动画。我已经成功地使用
CAKeyframeAnimation(keyPath:“position”)
沿圆圈360度设置了两个拇指(绿色)的动画,但我的时间范围(红色)
CAShapeLayer
无法做到这一点。我最好的办法是使用
CABasicAnimation(keyPath:“path”)
设置范围层的动画,但它在动画过程中变形了,没有沿着圆弧,而是以直线向量穿过了圆

为了简洁起见,我将避免发布
getStuff()
函数的内容,因为它们与问题无关

// This works
let startAngle: CGFloat = getAngle(for: start)
let startPath: UIBezierPath = getThumbTravelPath(fromAngle: startThumbAngle, toAngle: startAngle)
let startAnimation = CAKeyframeAnimation(keyPath: "position")
startAnimation.path = startPath.cgPath
startAnimation.isRemovedOnCompletion = false
startAnimation.calculationMode = .cubicPaced
startAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
startAnimation.duration = 0.2
startThumbView.layer.add(startAnimation, forKey: nil)
    
let endAngle: CGFloat = getAngle(for: end)
let endPath: UIBezierPath = getThumbTravelPath(fromAngle: endThumbAngle, toAngle: endAngle)
let endAnimation = CAKeyframeAnimation(keyPath: "position")
endAnimation.path = endPath.cgPath
endAnimation.isRemovedOnCompletion = false
endAnimation.calculationMode = .cubicPaced
endAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
endAnimation.duration = 0.2
endThumbView.layer.add(endAnimation, forKey: nil)

// This doesn't work
let path: UIBezierPath = getRangePathFor(startAngle: startAngle, endAngle: endAngle)
let pathAnimation = CABasicAnimation(keyPath: "path")
pathAnimation.fromValue = rangePath.cgPath 
pathAnimation.toValue = path.cgPath
pathAnimation.duration = 0.2
pathAnimation.isRemovedOnCompletion = false
pathAnimation.isAdditive = true
rangeLayer.path = path.cgPath
rangeLayer.add(pathAnimation, forKey: nil)
我见过一些人建议使用
cabasicanitation(keyPath:“startStroke”)
+
cabasicanitation(keyPath:“endStroke”)
方法,但据我所知,这是行不通的,因为
startStroke
不能有负值,因此,我不能让弧从270°延伸到45°。

看起来像
strokeStart
&
strokeEnd
毕竟是答案


解决方案 我通过旋转
rangeLayer
来解决这个问题,旋转方式始终与开始拇指的角度一致,因此我的
strokeStart
始终为零。作为下一步,我只需从
endAngle
(以度而不是弧度为单位)中减去
startAngle
,然后用它来计算
endStroke
的值

    private func setRangeLayer(startRadian: CGFloat, endRadian: CGFloat, animated: Bool, duration: CFTimeInterval? = nil) {
    let endAngle = endRadian / .pi * 180
    let endStroke = endAngle / 360
    let rotation = CGAffineTransform(rotationAngle: startRadian + .pi / 2)
    
    CATransaction.begin()
    if !animated {
        CATransaction.disableActions()
        CATransaction.setAnimationDuration(0)
    } else {
        CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: .easeInEaseOut))
        if let duration = duration {
            CATransaction.setAnimationDuration(duration)
        }
     }
    rangeLayer.strokeStart = 0
    rangeLayer.strokeEnd = endStroke
    rangeLayer.setAffineTransform(rotation)
    CATransaction.commit()
    }
    let startAngle: CGFloat = getAngle(for: startTimestamp)
    let endAngle: CGFloat = getAngle(for: endTimestamp)
    let startRadian: CGFloat = (startAngle * .pi / 180) - .pi / 2
    let endRadian: CGFloat = (endAngle - startAngle) * .pi / 180
    setRangeLayer(startRadian: startRadian, endRadian: endRadian, animated: true, duration: animationDuration)
例子 我将从度转换为弧度,然后再转换回度的原因是为了在整个类中对不同用例的函数进行标准化。但是,对于
旋转
结束笔划
可以同样轻松地使用弧度

    private func setRangeLayer(startRadian: CGFloat, endRadian: CGFloat, animated: Bool, duration: CFTimeInterval? = nil) {
    let endAngle = endRadian / .pi * 180
    let endStroke = endAngle / 360
    let rotation = CGAffineTransform(rotationAngle: startRadian + .pi / 2)
    
    CATransaction.begin()
    if !animated {
        CATransaction.disableActions()
        CATransaction.setAnimationDuration(0)
    } else {
        CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: .easeInEaseOut))
        if let duration = duration {
            CATransaction.setAnimationDuration(duration)
        }
     }
    rangeLayer.strokeStart = 0
    rangeLayer.strokeEnd = endStroke
    rangeLayer.setAffineTransform(rotation)
    CATransaction.commit()
    }
    let startAngle: CGFloat = getAngle(for: startTimestamp)
    let endAngle: CGFloat = getAngle(for: endTimestamp)
    let startRadian: CGFloat = (startAngle * .pi / 180) - .pi / 2
    let endRadian: CGFloat = (endAngle - startAngle) * .pi / 180
    setRangeLayer(startRadian: startRadian, endRadian: endRadian, animated: true, duration: animationDuration)
重要提示 为了正确地
围绕其中心旋转
,您必须事先设置其
框架
/
边界
,否则它将像飞机旋翼一样沿原点旋转



希望这能帮助处于类似情况的人。

为什么不将270º设置为405º?问题是
开始行程
&
结束行程
的范围仅为0.0到1.0,这意味着我不能从0.75到1.125