CABasicAnimation CALayer 路径动画方向
CABasicAnimation CALayer Path Animation Direction
我正在尝试使用 CABasicAnimation
关键路径为图层路径 属性 设置动画 但是。没有得到确切的结果
下面是我要找的路径变化动画
预期动画
注意:-请忽略开关..我的重点只是从circular
到moon
动画的路径变化
我试过的
这是我尝试获取此路径转换动画的代码
@IBDesignable class CustomView: UIView {
private lazy var shapeLayer = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
shapeLayer.frame = bounds
shapeLayer.fillColor = UIColor.black.cgColor
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.lineWidth = 3
layer.addSublayer(shapeLayer)
}
override func layoutSubviews() {
shapeLayer.path = drawSunShape(bounds)
}
private func drawSunShape(_ group:CGRect) -> CGPath {
let bezierPath = UIBezierPath()
let radius = group.maxX/2
let center = CGPoint(x: group.midX, y: group.midY)
bezierPath.addArc(withCenter:center, radius: radius, startAngle: -.pi/2, endAngle: 3 * .pi * 0.5 , clockwise: true)
return bezierPath.cgPath
}
private func drawMoonShape(_ group:CGRect) -> CGPath {
let rad : CGFloat = group.maxX/2
let center = CGPoint(x: group.midX, y: group.midY)
let big = UIBezierPath()
big.addArc(withCenter: center, radius: rad, startAngle:-.pi/2.0, endAngle: .pi/2.0, clockwise: true)
big.addArc(withCenter: CGPoint(x: rad * cos(.pi/2.0), y: center.y), radius: rad * sin(.pi/2.0), startAngle: .pi/2.0, endAngle: -.pi/2.0, clockwise: false)
big.close()
return big.cgPath
}
private func animateToOffShape() {
let anim = CABasicAnimation(keyPath: "path")
anim.toValue = drawMoonShape(bounds)
anim.duration = 5.0
shapeLayer.add(anim, forKey: nil)
}
}
我从这段代码中得到了什么
更新
在@sweeper 和@max 的帮助下,我能够做到这一点
但我想从中间靠右
任何指示我如何才能达到预期的结果。谢谢
这两条路径的定义方式很重要。 iOS 不够智能,无法在两个任意路径之间自动创建 good-looking 转换。两者在建设中越接近越好过渡。
例如,如果你这样构造你的月亮
你可以这样构建你的圈子
请注意,这两个形状都有四个圆弧,并且形成大部分圆的两个大圆弧在两个形状之间是相同的(只是旋转了一点)。特别是如果您使两个形状之间的圆弧顺序保持一致,iOS 会更好地创建动画。
如 Max 所述,当两条路径具有相同数量的点时,贝塞尔路径插值效果最佳。
这可能会让您接近您想要的...
想法是将圆分成两条弧线——保持一条弧线不变,修改另一条弧线:
为了计算弧度,我们从一个完整的圆和我们想要的新月开始:
为了得到月牙点,我们可以把原来的整圆把它的中心向上和向左移动(半径的1/3给出一个不错的结果):
所以,我们想把圆分成两条弧...
我们的第一个圆弧将从 301° 开始,顺时针转到 149°:
这意味着我们的第二个圆弧将从 149° 开始,顺时针转到 301°:
下一步是获取“修改后的”第二条弧线:
这里我们的第二个圆弧将从 121° 开始,然后 counter-clockwise 到 329°:
结果:
这是一些示例代码,具有 hard-coded 个角度。我将留给您做数学运算以获得不同月牙形状的精确角度。每次点击都会在“太阳”和“月亮”形状之间设置动画 back-and-forth:
// little degrees-to-radians helper
extension BinaryInteger {
var degreesToRadians: CGFloat { CGFloat(self) * .pi / 180 }
}
class MoonView: UIView {
private lazy var shapeLayer = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
shapeLayer.fillColor = UIColor.cyan.cgColor
// to see outline only
//shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.lineWidth = 3
shapeLayer.lineJoin = .round
layer.addSublayer(shapeLayer)
}
private var isOn: Bool = true
override func layoutSubviews() {
super.layoutSubviews()
shapeLayer.frame = bounds
if isOn {
shapeLayer.path = drawSunShape(bounds)
} else {
shapeLayer.path = drawMoonShape(bounds)
}
}
private func drawSunShape(_ group:CGRect) -> CGPath {
let radius = group.maxX/2
let center = CGPoint(x: group.midX, y: group.midY)
let bezierPath = UIBezierPath()
bezierPath.addArc(withCenter: center, radius: radius, startAngle: 301.degreesToRadians, endAngle: 149.degreesToRadians, clockwise: true)
bezierPath.addArc(withCenter: center, radius: radius, startAngle: 149.degreesToRadians, endAngle: 301.degreesToRadians, clockwise: true)
bezierPath.close()
return bezierPath.cgPath
}
private func drawMoonShape(_ group:CGRect) -> CGPath {
let radius: CGFloat = group.maxX/2
let centerA = CGPoint(x: group.midX, y: group.midY)
let offset = radius * 1.0 / 3.0
let centerB = CGPoint(x: centerA.x - offset, y: centerA.y - offset)
let bezierPath = UIBezierPath()
bezierPath.addArc(withCenter: centerA, radius: radius, startAngle: 301.degreesToRadians, endAngle: 149.degreesToRadians, clockwise: true)
bezierPath.addArc(withCenter: centerB, radius: radius, startAngle: 121.degreesToRadians, endAngle: 329.degreesToRadians, clockwise: false)
bezierPath.close()
return bezierPath.cgPath
}
func animateShape() {
let anim = CABasicAnimation(keyPath: "path")
if isOn {
anim.toValue = drawMoonShape(bounds)
} else {
anim.toValue = drawSunShape(bounds)
}
anim.duration = 0.5
anim.fillMode = .forwards
anim.isRemovedOnCompletion = false
shapeLayer.add(anim, forKey: nil)
isOn.toggle()
}
}
class MoonViewController: UIViewController {
let mView = MoonView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .yellow
view.addSubview(mView)
mView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(mView)
NSLayoutConstraint.activate([
mView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
mView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
mView.widthAnchor.constraint(equalToConstant: 200.0),
mView.heightAnchor.constraint(equalTo: mView.widthAnchor)
])
mView.backgroundColor = .white
let t = UITapGestureRecognizer(target: self, action: #selector(self.didTap(_:)))
view.addGestureRecognizer(t)
}
@objc func didTap(_ g: UITapGestureRecognizer) -> Void {
mView.animateShape()
}
}
我正在尝试使用 CABasicAnimation
关键路径为图层路径 属性 设置动画 但是。没有得到确切的结果
下面是我要找的路径变化动画
预期动画
注意:-请忽略开关..我的重点只是从circular
到moon
动画的路径变化
我试过的
这是我尝试获取此路径转换动画的代码
@IBDesignable class CustomView: UIView {
private lazy var shapeLayer = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
shapeLayer.frame = bounds
shapeLayer.fillColor = UIColor.black.cgColor
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.lineWidth = 3
layer.addSublayer(shapeLayer)
}
override func layoutSubviews() {
shapeLayer.path = drawSunShape(bounds)
}
private func drawSunShape(_ group:CGRect) -> CGPath {
let bezierPath = UIBezierPath()
let radius = group.maxX/2
let center = CGPoint(x: group.midX, y: group.midY)
bezierPath.addArc(withCenter:center, radius: radius, startAngle: -.pi/2, endAngle: 3 * .pi * 0.5 , clockwise: true)
return bezierPath.cgPath
}
private func drawMoonShape(_ group:CGRect) -> CGPath {
let rad : CGFloat = group.maxX/2
let center = CGPoint(x: group.midX, y: group.midY)
let big = UIBezierPath()
big.addArc(withCenter: center, radius: rad, startAngle:-.pi/2.0, endAngle: .pi/2.0, clockwise: true)
big.addArc(withCenter: CGPoint(x: rad * cos(.pi/2.0), y: center.y), radius: rad * sin(.pi/2.0), startAngle: .pi/2.0, endAngle: -.pi/2.0, clockwise: false)
big.close()
return big.cgPath
}
private func animateToOffShape() {
let anim = CABasicAnimation(keyPath: "path")
anim.toValue = drawMoonShape(bounds)
anim.duration = 5.0
shapeLayer.add(anim, forKey: nil)
}
}
我从这段代码中得到了什么
更新
在@sweeper 和@max 的帮助下,我能够做到这一点
但我想从中间靠右
任何指示我如何才能达到预期的结果。谢谢
这两条路径的定义方式很重要。 iOS 不够智能,无法在两个任意路径之间自动创建 good-looking 转换。两者在建设中越接近越好过渡。
例如,如果你这样构造你的月亮
你可以这样构建你的圈子
请注意,这两个形状都有四个圆弧,并且形成大部分圆的两个大圆弧在两个形状之间是相同的(只是旋转了一点)。特别是如果您使两个形状之间的圆弧顺序保持一致,iOS 会更好地创建动画。
如 Max 所述,当两条路径具有相同数量的点时,贝塞尔路径插值效果最佳。
这可能会让您接近您想要的...
想法是将圆分成两条弧线——保持一条弧线不变,修改另一条弧线:
为了计算弧度,我们从一个完整的圆和我们想要的新月开始:
为了得到月牙点,我们可以把原来的整圆把它的中心向上和向左移动(半径的1/3给出一个不错的结果):
所以,我们想把圆分成两条弧...
我们的第一个圆弧将从 301° 开始,顺时针转到 149°:
这意味着我们的第二个圆弧将从 149° 开始,顺时针转到 301°:
下一步是获取“修改后的”第二条弧线:
这里我们的第二个圆弧将从 121° 开始,然后 counter-clockwise 到 329°:
结果:
这是一些示例代码,具有 hard-coded 个角度。我将留给您做数学运算以获得不同月牙形状的精确角度。每次点击都会在“太阳”和“月亮”形状之间设置动画 back-and-forth:
// little degrees-to-radians helper
extension BinaryInteger {
var degreesToRadians: CGFloat { CGFloat(self) * .pi / 180 }
}
class MoonView: UIView {
private lazy var shapeLayer = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
shapeLayer.fillColor = UIColor.cyan.cgColor
// to see outline only
//shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.lineWidth = 3
shapeLayer.lineJoin = .round
layer.addSublayer(shapeLayer)
}
private var isOn: Bool = true
override func layoutSubviews() {
super.layoutSubviews()
shapeLayer.frame = bounds
if isOn {
shapeLayer.path = drawSunShape(bounds)
} else {
shapeLayer.path = drawMoonShape(bounds)
}
}
private func drawSunShape(_ group:CGRect) -> CGPath {
let radius = group.maxX/2
let center = CGPoint(x: group.midX, y: group.midY)
let bezierPath = UIBezierPath()
bezierPath.addArc(withCenter: center, radius: radius, startAngle: 301.degreesToRadians, endAngle: 149.degreesToRadians, clockwise: true)
bezierPath.addArc(withCenter: center, radius: radius, startAngle: 149.degreesToRadians, endAngle: 301.degreesToRadians, clockwise: true)
bezierPath.close()
return bezierPath.cgPath
}
private func drawMoonShape(_ group:CGRect) -> CGPath {
let radius: CGFloat = group.maxX/2
let centerA = CGPoint(x: group.midX, y: group.midY)
let offset = radius * 1.0 / 3.0
let centerB = CGPoint(x: centerA.x - offset, y: centerA.y - offset)
let bezierPath = UIBezierPath()
bezierPath.addArc(withCenter: centerA, radius: radius, startAngle: 301.degreesToRadians, endAngle: 149.degreesToRadians, clockwise: true)
bezierPath.addArc(withCenter: centerB, radius: radius, startAngle: 121.degreesToRadians, endAngle: 329.degreesToRadians, clockwise: false)
bezierPath.close()
return bezierPath.cgPath
}
func animateShape() {
let anim = CABasicAnimation(keyPath: "path")
if isOn {
anim.toValue = drawMoonShape(bounds)
} else {
anim.toValue = drawSunShape(bounds)
}
anim.duration = 0.5
anim.fillMode = .forwards
anim.isRemovedOnCompletion = false
shapeLayer.add(anim, forKey: nil)
isOn.toggle()
}
}
class MoonViewController: UIViewController {
let mView = MoonView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .yellow
view.addSubview(mView)
mView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(mView)
NSLayoutConstraint.activate([
mView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
mView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
mView.widthAnchor.constraint(equalToConstant: 200.0),
mView.heightAnchor.constraint(equalTo: mView.widthAnchor)
])
mView.backgroundColor = .white
let t = UITapGestureRecognizer(target: self, action: #selector(self.didTap(_:)))
view.addGestureRecognizer(t)
}
@objc func didTap(_ g: UITapGestureRecognizer) -> Void {
mView.animateShape()
}
}