CABasicAnimation CALayer 路径动画方向

CABasicAnimation CALayer Path Animation Direction

我正在尝试使用 CABasicAnimation 关键路径为图层路径 属性 设置动画 但是。没有得到确切的结果

下面是我要找的路径变化动画

预期动画

注意:-请忽略开关..我的重点只是从circularmoon动画的路径变化

我试过的

这是我尝试获取此路径转换动画的代码

@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()
    }

}