Swift:沿动画路径动画对象
Posted
技术标签:
【中文标题】Swift:沿动画路径动画对象【英文标题】:Swift: Animate object along animating path 【发布时间】:2020-06-18 06:48:43 【问题描述】:我想动画小红点围绕以脉冲方式扩展的圆圈旋转(从小到大,然后从小开始)。似乎小点一直围绕原始形状旋转,并且没有考虑到它正在扩展的那个圆圈......我在代码中有这个:
// MARK: - Properties
private lazy var containerView = UIView()
let littleCircleRadius: CGFloat = 10
private lazy var littleRedDot: CALayer =
let layer = CALayer()
layer.backgroundColor = UIColor.red.cgColor
let littleDotSize = CGSize(width: 10, height: 10)
layer.frame = CGRect(x: containerView.bounds.center.x - littleDotSize.width / 2,
y: containerView.bounds.center.y - littleCircleRadius - littleDotSize.width/2 ,
width: littleDotSize.width,
height: littleDotSize.height)
return layer
()
private lazy var littleCircleLayer: CAShapeLayer =
let layer = CAShapeLayer()
layer.lineWidth = 1.5
layer.lineCap = .round
layer.strokeColor = UIColor.black.cgColor
layer.fillColor = UIColor.clear.cgColor
return layer
()
// MARK: - Setup
func setup()
view.addSubview(containerView)
containerView.frame = CGRect(x: 40, y: 200, width: 300, height: 300)
containerView.backgroundColor = UIColor.gray.withAlphaComponent(0.2)
littleCircleLayer.path = makeArcPath(arcCenter: containerView.bounds.center, radius: 10)
containerView.layer.addSublayer(littleCircleLayer)
containerView.layer.addSublayer(littleRedDot)
// MARK: - Animations
func animate()
CATransaction.begin()
CATransaction.setAnimationDuration(1.5)
animateLittleRedDotRotation()
animateCircleExpanding()
CATransaction.commit()
func animateLittleRedDotRotation()
let anim = CAKeyframeAnimation(keyPath: "position")
anim.duration = 1.5
anim.rotationMode = .rotateAuto
anim.repeatCount = Float.infinity
anim.path = littleCircleLayer.path
littleRedDot.add(anim, forKey: "rotate")
func animateCircleExpanding()
let maxCircle = makeArcPath(arcCenter: containerView.bounds.center, radius: 100)
let circleExpandingAnim = CABasicAnimation(keyPath: "path")
circleExpandingAnim.fromValue = littleCircleLayer.path
circleExpandingAnim.toValue = maxCircle
circleExpandingAnim.repeatCount = Float.infinity
circleExpandingAnim.duration = 1.5
littleCircleLayer.add(circleExpandingAnim, forKey: "pulseCircuitAnimation")
这会产生以下效果:
但是,我希望小点沿着扩大的圆圈路径旋转(因为它从小圆圈动画到大圆圈),而不是原来的小圆圈路径。有什么想法吗?
【问题讨论】:
更新小圆点的框架 .... 如果你可以分享全班...我将能够重新创建和检查 这是我操场的全部内容gist.github.com/ZaweSK/7c9c06307593c64e2c22cde78e9aa6cd 【参考方案1】:使用 CoreAnimation 根据路径为红点的位置设置动画假定路径没有变化。从理论上讲,您可以定义一个反映不断扩大的圆圈的螺旋路径。就个人而言,我只会使用CADisplayLink
,这是一个为屏幕刷新优化设计的特殊计时器,并完全停用 CoreAnimation 调用。例如
func startDisplayLink()
let displayLink = CADisplayLink(target: self, selector: #selector(handleDisplayLink(_:)))
displayLink.add(to: .main, forMode: .common)
@objc func handleDisplayLink(_ displayLink: CADisplayLink)
let percent = CGFloat(displayLink.timestamp).truncatingRemainder(dividingBy: duration) / duration
let radius = ...
let center = containerView.bounds.center
circleLayer.path = makeArcPath(arcCenter: center, radius: radius)
let angle = percent * .pi * 2
let dotCenter = CGPoint(x: center.x + cos(angle) * radius, y: center.y + sin(angle) * radius)
redDot.path = makeArcPath(arcCenter: dotCenter, radius: 5)
产生:
完整示例:
class ViewController: UIViewController
private let radiusRange: ClosedRange<CGFloat> = 10...100
private let duration: CGFloat = 1.5
private lazy var containerView: UIView =
let containerView = UIView()
containerView.translatesAutoresizingMaskIntoConstraints = false
return containerView
()
private lazy var redDot: CAShapeLayer =
let layer = CAShapeLayer()
layer.fillColor = UIColor.red.cgColor
return layer
()
private lazy var circleLayer: CAShapeLayer =
let layer = CAShapeLayer()
layer.lineWidth = 1.5
layer.strokeColor = UIColor.black.cgColor
layer.fillColor = UIColor.clear.cgColor
return layer
()
private weak var displayLink: CADisplayLink?
override func viewDidLoad()
super.viewDidLoad()
setup()
override func viewDidAppear(_ animated: Bool)
super.viewDidAppear(animated)
startDisplayLink()
override func viewDidDisappear(_ animated: Bool)
super.viewDidDisappear(animated)
stopDisplayLink()
// MARK: Private utility methods
private extension ViewController
func setup()
addContainer()
containerView.layer.addSublayer(circleLayer)
containerView.layer.addSublayer(redDot)
func addContainer()
view.addSubview(containerView)
NSLayoutConstraint.activate([
containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
containerView.topAnchor.constraint(equalTo: view.topAnchor),
containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
func makeArcPath(arcCenter: CGPoint, radius: CGFloat) -> CGPath
UIBezierPath(arcCenter: arcCenter, radius: radius, startAngle: 0, endAngle: .pi * 2, clockwise: true).cgPath
// MARK: - DisplayLink related methods
private extension ViewController
func startDisplayLink()
stopDisplayLink() // stop existing display link, if any
let displayLink = CADisplayLink(target: self, selector: #selector(handleDisplayLink(_:)))
displayLink.add(to: .main, forMode: .common)
self.displayLink = displayLink
func stopDisplayLink()
displayLink?.invalidate()
@objc func handleDisplayLink(_ displayLink: CADisplayLink)
let percent = CGFloat(displayLink.timestamp).truncatingRemainder(dividingBy: duration) / duration
let radius = radiusRange.percent(percent)
let center = containerView.bounds.center
circleLayer.path = makeArcPath(arcCenter: center, radius: radius)
let angle = percent * .pi * 2
let dotCenter = CGPoint(x: center.x + cos(angle) * radius, y: center.y + sin(angle) * radius)
redDot.path = makeArcPath(arcCenter: dotCenter, radius: 5)
// MARK: - CGRect extension
extension CGRect
var center: CGPoint return CGPoint(x: midX, y: midY)
// MARK: - ClosedRange extension
extension ClosedRange where Bound: FloatingPoint
func percent(_ percent: Bound) -> Bound
(upperBound - lowerBound) * percent + lowerBound
【讨论】:
这是非常好的方法 Rob。谢谢? 我还有一个问题。使用这种方法是否也可以使用某种定时功能,这样动画就不会用线性定时绘制?我的意思是,我能否得到相同的结果,但可以说 .easeIn。时机? (我真正需要的是由 Beziere 曲线 (0,0, 0.25, 1) 定义的自定义计时函数) 我相信你必须编写自己的缓动计时函数。有关贝塞尔示例,请参阅***.com/a/30454591/1271826。或者你可以自己做,例如,对于近似缓入和缓出的三角曲线,我使用 1 - cos(πt) 其中 t 在 [0 ,1]。以上是关于Swift:沿动画路径动画对象的主要内容,如果未能解决你的问题,请参考以下文章