Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
238 views
in Technique[技术] by (71.8m points)

ios - Create spinning circle loading animation

I'm trying to create a spinning circle loader animation like in the following Android project (https://github.com/pedant/sweet-alert-dialog).

enter image description here

I don't need the entire popup dialog - just the spinning part. It changes colors and spins indefinitly (until I choose to dismiss it).

I'm kind of new to swift and I've never been the kind to do animations. Here's what I have so far (found code in similar project for iOS):

The layers setup:

    outlineLayer.position = CGPointMake(0,
        0);
    outlineLayer.path = outlineCircle
    outlineLayer.fillColor = UIColor.clearColor().CGColor;
    outlineLayer.strokeColor = UIColor(red: 150.0/255.0, green: 216.0/255.0, blue: 115.0/255.0, alpha: 1.0).CGColor;
    outlineLayer.lineCap = kCALineCapRound
    outlineLayer.lineWidth = 4;
    outlineLayer.opacity = 0.1
    self.layer.addSublayer(outlineLayer)

    circleLayer.position = CGPointMake(0,
        0);
    circleLayer.path = path
    circleLayer.fillColor = UIColor.clearColor().CGColor;
    circleLayer.strokeColor = UIColor(red: 150.0/255.0, green: 216.0/255.0, blue: 115.0/255.0, alpha: 1.0).CGColor;
    circleLayer.lineCap = kCALineCapRound
    circleLayer.lineWidth = 4;
    circleLayer.actions = [
        "strokeStart": NSNull(),
        "strokeEnd": NSNull(),
        "transform": NSNull()
    ]
    self.layer.addSublayer(circleLayer)

Animation:

let strokeStart = CABasicAnimation(keyPath: "strokeStart")
    let strokeEnd = CABasicAnimation(keyPath: "strokeEnd")
    let factor = 0.545
    let timing = CAMediaTimingFunction(controlPoints: 0.3, 0.6, 0.8, 1.2)

    strokeEnd.fromValue = 0.00
    strokeEnd.toValue = 0.93
    strokeEnd.duration = 10.0 * factor
    strokeEnd.timingFunction = timing
    strokeEnd.autoreverses = true

    strokeStart.fromValue = 0.0
    strokeStart.toValue = 0.68
    strokeStart.duration =  10.0 * factor
    strokeStart.beginTime =  CACurrentMediaTime() + 3.0 * factor
    strokeStart.fillMode = kCAFillModeBackwards
    strokeStart.timingFunction = timing
    strokeStart.repeatCount = HUGE

    circleLayer.strokeStart = 0.68
    circleLayer.strokeEnd = 0.93

    self.circleLayer.addAnimation(strokeEnd, forKey: "strokeEnd")
    self.circleLayer.addAnimation(strokeStart, forKey: "strokeStart")

but what I have is not nearly close and I have no idea where to go from here. What I'm doing is changing a value and running seeing how it affects but I feel like I'm lost here.

How can I achieve such animation like in the example?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I didn't closely analyze the exact parameters of the animation, but this looks good to me:

spinner view

import UIKit

@IBDesignable
class SpinnerView : UIView {

    override var layer: CAShapeLayer {
        get {
            return super.layer as! CAShapeLayer
        }
    }

    override class var layerClass: AnyClass {
        return CAShapeLayer.self
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        layer.fillColor = nil
        layer.strokeColor = UIColor.black.cgColor
        layer.lineWidth = 3
        setPath()
    }

    override func didMoveToWindow() {
        animate()
    }

    private func setPath() {
        layer.path = UIBezierPath(ovalIn: bounds.insetBy(dx: layer.lineWidth / 2, dy: layer.lineWidth / 2)).cgPath
    }

    struct Pose {
        let secondsSincePriorPose: CFTimeInterval
        let start: CGFloat
        let length: CGFloat
        init(_ secondsSincePriorPose: CFTimeInterval, _ start: CGFloat, _ length: CGFloat) {
            self.secondsSincePriorPose = secondsSincePriorPose
            self.start = start
            self.length = length
        }
    }

    class var poses: [Pose] {
        get {
            return [
                Pose(0.0, 0.000, 0.7),
                Pose(0.6, 0.500, 0.5),
                Pose(0.6, 1.000, 0.3),
                Pose(0.6, 1.500, 0.1),
                Pose(0.2, 1.875, 0.1),
                Pose(0.2, 2.250, 0.3),
                Pose(0.2, 2.625, 0.5),
                Pose(0.2, 3.000, 0.7),
            ]
        }
    }

    func animate() {
        var time: CFTimeInterval = 0
        var times = [CFTimeInterval]()
        var start: CGFloat = 0
        var rotations = [CGFloat]()
        var strokeEnds = [CGFloat]()

        let poses = type(of: self).poses
        let totalSeconds = poses.reduce(0) { $0 + $1.secondsSincePriorPose }

        for pose in poses {
            time += pose.secondsSincePriorPose
            times.append(time / totalSeconds)
            start = pose.start
            rotations.append(start * 2 * .pi)
            strokeEnds.append(pose.length)
        }

        times.append(times.last!)
        rotations.append(rotations[0])
        strokeEnds.append(strokeEnds[0])

        animateKeyPath(keyPath: "strokeEnd", duration: totalSeconds, times: times, values: strokeEnds)
        animateKeyPath(keyPath: "transform.rotation", duration: totalSeconds, times: times, values: rotations)

        animateStrokeHueWithDuration(duration: totalSeconds * 5)
    }

    func animateKeyPath(keyPath: String, duration: CFTimeInterval, times: [CFTimeInterval], values: [CGFloat]) {
        let animation = CAKeyframeAnimation(keyPath: keyPath)
        animation.keyTimes = times as [NSNumber]?
        animation.values = values
        animation.calculationMode = .linear
        animation.duration = duration
        animation.repeatCount = Float.infinity
        layer.add(animation, forKey: animation.keyPath)
    }

    func animateStrokeHueWithDuration(duration: CFTimeInterval) {
        let count = 36
        let animation = CAKeyframeAnimation(keyPath: "strokeColor")
        animation.keyTimes = (0 ... count).map { NSNumber(value: CFTimeInterval($0) / CFTimeInterval(count)) }
        animation.values = (0 ... count).map {
            UIColor(hue: CGFloat($0) / CGFloat(count), saturation: 1, brightness: 1, alpha: 1).cgColor
        }
        animation.duration = duration
        animation.calculationMode = .linear
        animation.repeatCount = Float.infinity
        layer.add(animation, forKey: animation.keyPath)
    }

}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...