2个角度之间的动画UIBezierPath弧
Animate UIBezierPath arc between 2 angles
我构建了一个自定义的圆形范围选择器,类似于 iOS 的 Alarm/Bedtime 应用程序中的那个。为此,我使用了两个 UIButton
thumbs 并附有 UIPanGestureRecognizer
和 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)
我看到有些人建议使用 CABasicAnimation(keyPath: "startStroke")
+ CABasicAnimation(keyPath: "endStroke")
方法,但据我所知,这行不通,因为我不能为startStroke
,因此我不能有从 270° 到 45° 的弧度。
看起来 strokeStart
& strokeEnd
才是答案。
解决方案
我设法通过旋转 rangeLayer
本身来解决这个问题,其方式始终对应于 start thumb 的角度,所以我的 strokeStart
是总是零。作为下一步,我只是从 endAngle
(以度为单位,而不是弧度) 中减去 startAngle
并使用它来计算 [=18= 的值].
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()
}
例子
我将度数转换为弧度然后再转换回度数的原因是为了在整个 class 中针对不同用例对函数进行标准化。但是,您可以轻松地对 rotation
使用弧度,对 endStroke
.
使用度数
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)
重要提示
为了正确地 rotate
CAShapeLayer
围绕它的中心,你 必须 预先设置它的 frame
/ bounds
或者它会像飞机旋翼一样沿着原点旋转。
希望这对处于类似情况的人有所帮助。
我构建了一个自定义的圆形范围选择器,类似于 iOS 的 Alarm/Bedtime 应用程序中的那个。为此,我使用了两个 UIButton
thumbs 并附有 UIPanGestureRecognizer
和 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)
我看到有些人建议使用 CABasicAnimation(keyPath: "startStroke")
+ CABasicAnimation(keyPath: "endStroke")
方法,但据我所知,这行不通,因为我不能为startStroke
,因此我不能有从 270° 到 45° 的弧度。
看起来 strokeStart
& strokeEnd
才是答案。
解决方案
我设法通过旋转 rangeLayer
本身来解决这个问题,其方式始终对应于 start thumb 的角度,所以我的 strokeStart
是总是零。作为下一步,我只是从 endAngle
(以度为单位,而不是弧度) 中减去 startAngle
并使用它来计算 [=18= 的值].
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()
}
例子
我将度数转换为弧度然后再转换回度数的原因是为了在整个 class 中针对不同用例对函数进行标准化。但是,您可以轻松地对 rotation
使用弧度,对 endStroke
.
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)
重要提示
为了正确地 rotate
CAShapeLayer
围绕它的中心,你 必须 预先设置它的 frame
/ bounds
或者它会像飞机旋翼一样沿着原点旋转。
希望这对处于类似情况的人有所帮助。