问题描述
我建立了一个自定义的圆形范围选择器,类似于iOS的Alarm / bedtime应用程序中的那个。为此,我使用了两个UIButton
附加了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")
方法,但据我所知,这是行不通的,因为我不能为{{ 1}},因此我无法将弧形从270°延伸到45°。
解决方法
看起来像strokeStart
和strokeEnd
才是答案。
解决方案
我设法通过旋转rangeLayer
本身来解决此问题,该方式始终与起始拇指的角度相符,所以我的strokeStart
始终为零。下一步,我只是从startAngle
中减去endAngle
(以度为单位,不是弧度),并使用它来计算endStroke
的值。 / p>
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()
}
示例
我将度数转换为弧度,然后再转换回度数的原因是为了使整个类中的不同用例的功能标准化。但是,您可以轻松地将弧度用于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
,否则它将沿其中心旋转而是像飞机转子一样起源。
希望这会帮助处于类似情况的人。