簡體   English   中英

使用圓UIBezierPath和CABasicAnimation為CAShapeLayer設置動畫

[英]Animate CAShapeLayer with circle UIBezierPath and CABasicAnimation

我想在15秒內將角度從0度設置為360度。

動畫很怪異。 我知道這可能是一個開始/結束角度問題,我已經遇到了圓形動畫的那種問題,但我不知道如何解決這個問題。

var circle_layer=CAShapeLayer()
var circle_anim=CABasicAnimation(keyPath: "path")

func init_circle_layer(){
    let w=circle_view.bounds.width
    let center=CGPoint(x: w/2, y: w/2)

    //initial path
    let start_angle:CGFloat = -0.25*360*CGFloat.pi/180
    let initial_path=UIBezierPath(arcCenter: center, radius: w/2, startAngle: start_angle, endAngle: start_angle, clockwise: true)
    initial_path.addLine(to: center)

    //final path
    let end_angle:CGFloat=start_angle+360*CGFloat(CGFloat.pi/180)
    let final_path=UIBezierPath(arcCenter: center, radius: w/2, startAngle: start_angle, endAngle: end_angle, clockwise: true)
    final_path.addLine(to: center)

    //init layer
    circle_layer.path=initial_path.cgPath
    circle_layer.fillColor=UIColor(hex_code: "EA535D").cgColor
    circle_view.layer.addSublayer(circle_layer)

    //init anim
    circle_anim.duration=15
    circle_anim.fromValue=initial_path.cgPath
    circle_anim.toValue=final_path.cgPath
    circle_anim.isRemovedOnCompletion=false
    circle_anim.fillMode=kCAFillModeForwards
    circle_anim.delegate=self
}

func start_circle_animation(){
    circle_layer.add(circle_anim, forKey: "circle_anim")
}

我希望從0度開始頂部並在完整巡回賽后完成頂部: 在此輸入圖像描述

在此輸入圖像描述

你不能輕易地為UIBezierPath的填充設置動畫(或者至少在沒有引入奇怪的工件的情況下,除非在控制良好的情況下)。 但是您可以為strokeEndpathCAShapeLayer設置動畫。 如果你使描邊路徑的線寬非常寬(即最后一個圓的半徑),並將路徑的半徑設置為圓的半徑,那么你會得到類似於你正在尋找的東西。

private var circleLayer = CAShapeLayer()

private func configureCircleLayer() {
    let radius = min(circleView.bounds.width, circleView.bounds.height) / 2

    circleLayer.strokeColor = UIColor(hexCode: "EA535D").cgColor
    circleLayer.fillColor = UIColor.clear.cgColor
    circleLayer.lineWidth = radius
    circleView.layer.addSublayer(circleLayer)

    let center = CGPoint(x: circleView.bounds.width/2, y: circleView.bounds.height/2)
    let startAngle: CGFloat = -0.25 * 2 * .pi
    let endAngle: CGFloat = startAngle + 2 * .pi
    circleLayer.path = UIBezierPath(arcCenter: center, radius: radius / 2, startAngle: startAngle, endAngle: endAngle, clockwise: true).cgPath

    circleLayer.strokeEnd = 0
}

private func startCircleAnimation() {
    circleLayer.strokeEnd = 1
    let animation = CABasicAnimation(keyPath: "strokeEnd")
    animation.fromValue = 0
    animation.toValue = 1
    animation.duration = 15
    circleLayer.add(animation, forKey: nil)
}

為了最終控制,在執行復雜的UIBezierPath動畫時,您可以使用CADisplayLink ,避免在使用path CABasicAnimation時有時會產生的工件:

private var circleLayer = CAShapeLayer()
private weak var displayLink: CADisplayLink?
private var startTime: CFTimeInterval!

private func configureCircleLayer() {
    circleLayer.fillColor = UIColor(hexCode: "EA535D").cgColor
    circleView.layer.addSublayer(circleLayer)
    updatePath(percent: 0)
}

private func startCircleAnimation() {
    startTime = CACurrentMediaTime()
    displayLink = {
        let _displayLink = CADisplayLink(target: self, selector: #selector(handleDisplayLink(_:)))
        _displayLink.add(to: .current, forMode: .commonModes)
        return _displayLink
    }()
}

@objc func handleDisplayLink(_ displayLink: CADisplayLink) {   // the @objc qualifier needed for Swift 4 @objc inference
    let percent = CGFloat(CACurrentMediaTime() - startTime) / 15.0
    updatePath(percent: min(percent, 1.0))
    if percent > 1.0 {
        displayLink.invalidate()
    }
}

private func updatePath(percent: CGFloat) {
    let w = circleView.bounds.width
    let center = CGPoint(x: w/2, y: w/2)
    let startAngle: CGFloat = -0.25 * 2 * .pi
    let endAngle: CGFloat = startAngle + percent * 2 * .pi
    let path = UIBezierPath()
    path.move(to: center)
    path.addArc(withCenter: center, radius: w/2, startAngle: startAngle, endAngle: endAngle, clockwise: true)
    path.close()

    circleLayer.path = path.cgPath
}

然后你可以這樣做:

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    configureCircleLayer()
    startCircleAnimation()
}

override func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)

    displayLink?.invalidate()   // to avoid displaylink keeping a reference to dismissed view during animation
}

產量:

動畫圈

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM