ios - CABasicAnimation CALayer 路径动画方向

标签 ios swift uibezierpath cashapelayer cabasicanimation

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

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

预期的动画

enter image description here

注意:-请忽略开关..我的重点只是从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 的帮助下实现这一点

enter image description here

但我想从中间靠右

任何指示我如何能达到预期的结果。谢谢

最佳答案

正如 Max 所说,当两条路径具有相同数量的点时,贝塞尔路径插值效果最佳。

这可能会让您接近您想要的...

想法是将圆分成两条弧线——保持一条弧线不变,修改另一条弧线:

为了计算弧度,我们从一个完整的圆和我们想要的新月开始:

enter image description here

enter image description here

为了得到月牙点,我们可以取原来的整圆并将其中心向上和向左移动(半径的 1/3 给出了一个不错的结果):

enter image description here

所以,我们想把圆分成两条弧...

enter image description here

我们的第一个圆弧将从 301° 开始,顺时针转到 149°:

enter image description here

这意味着我们的第二个圆弧将从 149° 开始并顺时针转到 301°:

enter image description here

下一步是获取“修改后的”第二条弧线:

enter image description here

这里我们的第二个弧将从 121° 开始,然后逆时针 到 329°:

enter image description here

结果:

enter image description here

enter image description here

这里是一些示例代码,带有硬编码的角度。我将留给您做数学运算以获得不同月牙形状的精确角度。每次点击都会在“太阳”和“月亮”形状之间来回动画:

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

}

关于ios - CABasicAnimation CALayer 路径动画方向,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63468314/

相关文章:

swift - 如何在 swift 中将 View 设置为之前状态的 90 度?

ios - 如何在我的 swift 应用程序中对绘制 svg 路径进行动画处理?

ios - 在 JSQMessageView Controller 中添加自定义 header

iphone - 禁止 UISlider 滑动,但仍然允许它接收 UIControlEventTouchDown

ios - 来自扩展的 UIAlertController

objective-c - iOS - 如何使用 UISlider 控制 UIBezierPath 绘图?

ios - 获取drawRect中UISlider的当前值 : method

ios - 具有 Storyboard Segues 的上下文菜单预览提供程序

ios - Firestore 不返回查询 Swift 5 Xcode 11.3

ios - Xcode 9.1 警告 "Language directional layout margins before iOS 11.0"