CABasicAnimation 开始时出现故障
Glitch at start of CABasicAnimation
我有一个 scaling/pulsing 动画,它在 for 循环中处理 cgPaths,如下所示。此代码有效,但仅当您将 animatedLayers
附加到 circleLayer
路径时,当 circleLayer
已添加到 subLayer
并且这会创建一个静态圆(类似故障) 在动画 (DispatchQueue
) 开始之前...
...
self.layer.addSublayer(animatedLayer)
animatedLayers.append(animatedLayer)
...
是否可以将带参数的 CAShapeLayer
添加到子层?如果没有,有什么推荐的替代方案吗?
import UIKit
import Foundation
@IBDesignable
class AnimatedCircleView: UIView {
// MARK: - Initializers
var animatedLayers = [CAShapeLayer]()
// MARK: - Methods
override func draw(_ rect: CGRect) {
// Animated circle
for _ in 0...3 {
let animatedPath = UIBezierPath(arcCenter: .zero, radius: self.layer.bounds.size.width / 2.3,
startAngle: 0, endAngle: 2 * CGFloat.pi, clockwise: true)
let animatedLayer = CAShapeLayer()
animatedLayer.path = animatedPath.cgPath
animatedLayer.strokeColor = UIColor.black.cgColor
animatedLayer.lineWidth = 0
animatedLayer.fillColor = UIColor.gray.cgColor
animatedLayer.lineCap = CAShapeLayerLineCap.round
animatedLayer.position = CGPoint(x: self.layer.bounds.size.width / 2, y: self.layer.bounds.size.width / 2)
self.layer.addSublayer(animatedLayer)
animatedLayers.append(animatedLayer)
}
// Dispatch animation for circle _ 0...3
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.animateCircle(index: 0)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
self.animateCircle(index: 1)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
self.animateCircle(index: 2)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
self.animateCircle(index: 3)
}
}
}
}
}
func animateCircle(index: Int) {
let scaleAnimation = CABasicAnimation(keyPath: "transform.scale")
scaleAnimation.duration = 1.8
scaleAnimation.fromValue = 0
scaleAnimation.toValue = 1
scaleAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
scaleAnimation.repeatCount = Float.infinity
animatedLayers[index].add(scaleAnimation, forKey: "scale")
let opacityAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))
opacityAnimation.duration = 1.8
opacityAnimation.fromValue = 0.7
opacityAnimation.toValue = 0
opacityAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
opacityAnimation.repeatCount = Float.infinity
animatedLayers[index].add(opacityAnimation, forKey: "opacity")
}
}
您是否尝试过在动画开始时将填充颜色设为透明,然后将其变为灰色?
// Animated circle
for _ in 0...3 {
let animatedPath = UIBezierPath(arcCenter: .zero, radius: self.layer.bounds.size.width / 2.3,
startAngle: 0, endAngle: 2 * CGFloat.pi, clockwise: true)
let animatedLayer = CAShapeLayer()
animatedLayer.path = animatedPath.cgPath
animatedLayer.strokeColor = UIColor.black.cgColor
animatedLayer.lineWidth = 0
animatedLayer.fillColor = UIColor.clear.cgColor
animatedLayer.lineCap = CAShapeLayerLineCap.round
animatedLayer.position = CGPoint(x: self.layer.bounds.size.width / 2, y: self.layer.bounds.size.width / 2)
self.layer.addSublayer(animatedLayer)
animatedLayers.append(animatedLayer)
}
// Dispatch animation for circle _ 0...3
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.animateCircle(index: 0)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
self.animateCircle(index: 1)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
self.animateCircle(index: 2)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
self.animateCircle(index: 3)
}
}
}
}
}
关键问题是您的动画开始时有交错的延迟,在 0.1 到 1.0 秒之间。在最后一个动画开始之前,该层只是坐在那里,全尺寸和 100% 不透明度。
由于您将变换比例设置为从 0 到 1 的动画,我建议将起始变换设置为 0(或将 opacity
更改为 0)。然后在他们各自的动画开始之前你不会看到他们坐在那里。
其他一些观察结果:
draw(_:)
不是添加图层、启动动画等的正确位置。此方法可能会被调用多次,并且应该代表给定时间点的视图。我会退休 draw(_:)
是不是开始这个的地方不合适而且你根本不需要这个方法。
你在 0.1 秒后开始你的第一个动画。为什么不立即开始呢?
您应该通过将 path
和 position
属性的设置推迟到 layoutSubviews
来处理帧调整。
因此:
@IBDesignable
class AnimatedCircleView: UIView {
private var animatedLayers = [CAShapeLayer]()
override init(frame: CGRect = .zero) {
super.init(frame: frame)
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
configure()
}
override func layoutSubviews() {
super.layoutSubviews()
let path = UIBezierPath(arcCenter: .zero, radius: bounds.width / 2.3, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
for animatedLayer in animatedLayers {
animatedLayer.path = path.cgPath
animatedLayer.position = CGPoint(x: bounds.midX, y: bounds.midY)
}
}
}
// MARK: - Methods
private extension AnimatedCircleView {
func configure() {
for _ in 0...3 {
let animatedLayer = CAShapeLayer()
animatedLayer.strokeColor = UIColor.black.cgColor
animatedLayer.lineWidth = 0
animatedLayer.fillColor = UIColor.gray.cgColor
animatedLayer.lineCap = .round
animatedLayer.transform = CATransform3DMakeScale(0, 0, 1)
layer.addSublayer(animatedLayer)
animatedLayers.append(animatedLayer)
}
self.animateCircle(index: 0)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.animateCircle(index: 1)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
self.animateCircle(index: 2)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
self.animateCircle(index: 3)
}
}
}
}
func animateCircle(index: Int) {
let scaleAnimation = CABasicAnimation(keyPath: "transform.scale")
scaleAnimation.duration = 1.8
scaleAnimation.fromValue = 0
scaleAnimation.toValue = 1
scaleAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
scaleAnimation.repeatCount = .greatestFiniteMagnitude
animatedLayers[index].add(scaleAnimation, forKey: "scale")
let opacityAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))
opacityAnimation.duration = 1.8
opacityAnimation.fromValue = 0.7
opacityAnimation.toValue = 0
opacityAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
opacityAnimation.repeatCount = .greatestFiniteMagnitude
animatedLayers[index].add(opacityAnimation, forKey: "opacity")
}
}
我有一个 scaling/pulsing 动画,它在 for 循环中处理 cgPaths,如下所示。此代码有效,但仅当您将 animatedLayers
附加到 circleLayer
路径时,当 circleLayer
已添加到 subLayer
并且这会创建一个静态圆(类似故障) 在动画 (DispatchQueue
) 开始之前...
...
self.layer.addSublayer(animatedLayer)
animatedLayers.append(animatedLayer)
...
是否可以将带参数的 CAShapeLayer
添加到子层?如果没有,有什么推荐的替代方案吗?
import UIKit
import Foundation
@IBDesignable
class AnimatedCircleView: UIView {
// MARK: - Initializers
var animatedLayers = [CAShapeLayer]()
// MARK: - Methods
override func draw(_ rect: CGRect) {
// Animated circle
for _ in 0...3 {
let animatedPath = UIBezierPath(arcCenter: .zero, radius: self.layer.bounds.size.width / 2.3,
startAngle: 0, endAngle: 2 * CGFloat.pi, clockwise: true)
let animatedLayer = CAShapeLayer()
animatedLayer.path = animatedPath.cgPath
animatedLayer.strokeColor = UIColor.black.cgColor
animatedLayer.lineWidth = 0
animatedLayer.fillColor = UIColor.gray.cgColor
animatedLayer.lineCap = CAShapeLayerLineCap.round
animatedLayer.position = CGPoint(x: self.layer.bounds.size.width / 2, y: self.layer.bounds.size.width / 2)
self.layer.addSublayer(animatedLayer)
animatedLayers.append(animatedLayer)
}
// Dispatch animation for circle _ 0...3
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.animateCircle(index: 0)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
self.animateCircle(index: 1)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
self.animateCircle(index: 2)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
self.animateCircle(index: 3)
}
}
}
}
}
func animateCircle(index: Int) {
let scaleAnimation = CABasicAnimation(keyPath: "transform.scale")
scaleAnimation.duration = 1.8
scaleAnimation.fromValue = 0
scaleAnimation.toValue = 1
scaleAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
scaleAnimation.repeatCount = Float.infinity
animatedLayers[index].add(scaleAnimation, forKey: "scale")
let opacityAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))
opacityAnimation.duration = 1.8
opacityAnimation.fromValue = 0.7
opacityAnimation.toValue = 0
opacityAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
opacityAnimation.repeatCount = Float.infinity
animatedLayers[index].add(opacityAnimation, forKey: "opacity")
}
}
您是否尝试过在动画开始时将填充颜色设为透明,然后将其变为灰色?
// Animated circle
for _ in 0...3 {
let animatedPath = UIBezierPath(arcCenter: .zero, radius: self.layer.bounds.size.width / 2.3,
startAngle: 0, endAngle: 2 * CGFloat.pi, clockwise: true)
let animatedLayer = CAShapeLayer()
animatedLayer.path = animatedPath.cgPath
animatedLayer.strokeColor = UIColor.black.cgColor
animatedLayer.lineWidth = 0
animatedLayer.fillColor = UIColor.clear.cgColor
animatedLayer.lineCap = CAShapeLayerLineCap.round
animatedLayer.position = CGPoint(x: self.layer.bounds.size.width / 2, y: self.layer.bounds.size.width / 2)
self.layer.addSublayer(animatedLayer)
animatedLayers.append(animatedLayer)
}
// Dispatch animation for circle _ 0...3
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.animateCircle(index: 0)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
self.animateCircle(index: 1)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
self.animateCircle(index: 2)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
self.animateCircle(index: 3)
}
}
}
}
}
关键问题是您的动画开始时有交错的延迟,在 0.1 到 1.0 秒之间。在最后一个动画开始之前,该层只是坐在那里,全尺寸和 100% 不透明度。
由于您将变换比例设置为从 0 到 1 的动画,我建议将起始变换设置为 0(或将 opacity
更改为 0)。然后在他们各自的动画开始之前你不会看到他们坐在那里。
其他一些观察结果:
draw(_:)
不是添加图层、启动动画等的正确位置。此方法可能会被调用多次,并且应该代表给定时间点的视图。我会退休draw(_:)
是不是开始这个的地方不合适而且你根本不需要这个方法。你在 0.1 秒后开始你的第一个动画。为什么不立即开始呢?
您应该通过将
path
和position
属性的设置推迟到layoutSubviews
来处理帧调整。
因此:
@IBDesignable
class AnimatedCircleView: UIView {
private var animatedLayers = [CAShapeLayer]()
override init(frame: CGRect = .zero) {
super.init(frame: frame)
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
configure()
}
override func layoutSubviews() {
super.layoutSubviews()
let path = UIBezierPath(arcCenter: .zero, radius: bounds.width / 2.3, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
for animatedLayer in animatedLayers {
animatedLayer.path = path.cgPath
animatedLayer.position = CGPoint(x: bounds.midX, y: bounds.midY)
}
}
}
// MARK: - Methods
private extension AnimatedCircleView {
func configure() {
for _ in 0...3 {
let animatedLayer = CAShapeLayer()
animatedLayer.strokeColor = UIColor.black.cgColor
animatedLayer.lineWidth = 0
animatedLayer.fillColor = UIColor.gray.cgColor
animatedLayer.lineCap = .round
animatedLayer.transform = CATransform3DMakeScale(0, 0, 1)
layer.addSublayer(animatedLayer)
animatedLayers.append(animatedLayer)
}
self.animateCircle(index: 0)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.animateCircle(index: 1)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
self.animateCircle(index: 2)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
self.animateCircle(index: 3)
}
}
}
}
func animateCircle(index: Int) {
let scaleAnimation = CABasicAnimation(keyPath: "transform.scale")
scaleAnimation.duration = 1.8
scaleAnimation.fromValue = 0
scaleAnimation.toValue = 1
scaleAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
scaleAnimation.repeatCount = .greatestFiniteMagnitude
animatedLayers[index].add(scaleAnimation, forKey: "scale")
let opacityAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))
opacityAnimation.duration = 1.8
opacityAnimation.fromValue = 0.7
opacityAnimation.toValue = 0
opacityAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
opacityAnimation.repeatCount = .greatestFiniteMagnitude
animatedLayers[index].add(opacityAnimation, forKey: "opacity")
}
}