【问题标题】:Create spinning circle loading animation创建旋转圆圈加载动画
【发布时间】:2018-08-06 22:23:10
【问题描述】:

我正在尝试创建类似于以下 Android 项目 (https://github.com/pedant/sweet-alert-dialog) 中的旋转圆加载器动画。

我不需要整个弹出对话框 - 只是旋转部分。它会无限地改变颜色和旋转(直到我选择关闭它)。

我对 swift 有点陌生,而且我从来都不是那种做动画的人。这是我到目前为止所拥有的(在 iOS 的类似项目中找到代码):

图层设置:

    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)

动画:

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")

但我所拥有的还不是很接近,我不知道从这里去哪里。我正在做的是改变一个值并运行看看它是如何影响的,但我觉得我在这里迷路了。

我怎样才能像示例中那样实现这样的动画?

【问题讨论】:

  • 可能有一些开源项目/组件可以做你需要做的事情(你不需要重新发明以前做过的事情)。我在CocoaControls.com 上看到两个项目,比如GMDCircleLoaderCircleProgressView
  • 你找到解决方案了吗?
  • @Niharika 是的。你可以看到选择的答案。

标签: ios swift animation


【解决方案1】:

我没有仔细分析动画的确切参数,但这对我来说看起来不错:

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

}

【讨论】:

  • 看起来很棒!谢谢你!你有动画指南吗?
  • 我在模拟器中跑了代码,用LICEcap记录下来。
  • 对于 Swift 3:layerClass 现在在 Swift 中作为计算类属性导入。所以对于图层类,你应该使用这个:override class var layerClass: AnyClass { return CAShapeLayer.self }
  • 如何将它添加到我的视图控制器中? let spinner:SpinnerView = SpinnerView(frame: CGRect(x:0, y: 0, width: 120, height: 120)); self.view.addSubview(spinner); 我看不到它:/
  • @greenpoisononeTV 这不是动画本身的问题。如果应用程序进入非活动状态,UIKit 会停止正在进行的动画。如果需要,可以通过观察应用程序状态和反应动画来解决此问题。
【解决方案2】:

试试我的三个自定义加载器屏幕非常简单:

在 Viewcontoller.swift 文件中编写以下代码

class ViewController: UIViewController {

var signView = SignView(frame: CGRect.zero)
var testView = TestView(frame: CGRect.zero)
var testView1 = TestView1(frame: CGRect.zero)

override func viewDidLoad() {
    super.viewDidLoad()        
    self.view.backgroundColor = UIColor.orange
}


override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
      addSignView()
    //addTestView()
    //addTestView1()
}

func addSignView() { 
    signView.frame = CGRect(x: 0,
                            y: 0,
                            width: UIScreen.main.bounds.size.width,
                            height: UIScreen.main.bounds.size.height)
    self.view.addSubview(signView)
    signView.addAnimationLayer()
}

func addTestView() {
    let boxSize: CGFloat = 200.0

    testView.frame = CGRect(x: 16,
                            y: 350,
                            width: boxSize,
                            height: boxSize)
    self.view.addSubview(testView)
    testView.addAnimationLayer()
}

func addTestView1() {

    testView1.frame = CGRect(x: 0,
                            y: 0,
                            width: UIScreen.main.bounds.size.width,
                            height: UIScreen.main.bounds.size.height)
    self.view.addSubview(testView1)
    testView1.addAnimationLayer()
}}

现在添加 3 个文件继承与 UiView 命名 > SignView , TestView 和 TestView1

SignView.swift 文件的代码

class SignView: UIView {


let upCircleLayer = CAShapeLayer.init()
var path = UIBezierPath.init()
var animationDuration : Double = 2
var frameHeight : CGFloat = 50.0

override init(frame: CGRect) {
    super.init(frame: frame)

    self.backgroundColor = UIColor.black.withAlphaComponent(0.5)
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
}



var signWavePath : UIBezierPath {
    var clockCycle = true
    let yPoint = self.frame.size.height/2
    frameHeight =  self.frame.size.width/6
    for x in 1...24{

        if x%2 != 0 {

            let xpath =  UIBezierPath(arcCenter: CGPoint(x: CGFloat(x)*frameHeight/2, y: yPoint),
                         radius: frameHeight/2,
                         startAngle: 180.0 * .pi/180.0,
                         endAngle: 0.0,
                         clockwise: clockCycle)
            path.append(xpath)

            if(clockCycle){
                clockCycle = false
            }
            else{
                clockCycle = true
            }

        }
    }

    return path;
}

func addAnimationLayer() {


    // Add Upper Circle Layer
    upCircleLayer.fillColor = UIColor.clear.cgColor
    upCircleLayer.strokeColor = UIColor.white.cgColor
    upCircleLayer.lineWidth = 8.0
    upCircleLayer.path = signWavePath.cgPath
    layer.addSublayer(upCircleLayer)

    animateStrokeUpCircle()

    Timer.scheduledTimer(timeInterval: animationDuration, target: self, selector: #selector(animateStrokeUpCircle), userInfo: nil, repeats: true)
}


func animateStrokeUpCircle() {

    let strokeAnimation: CABasicAnimation = CABasicAnimation(keyPath: "strokeEnd")
        strokeAnimation.fromValue = 0.0
        strokeAnimation.toValue = 1.0
        strokeAnimation.duration = animationDuration
        strokeAnimation.isRemovedOnCompletion = false
        upCircleLayer.add(strokeAnimation, forKey: nil)

        expand1()
}
func expand1() {

        let expandAnimation: CABasicAnimation = CABasicAnimation(keyPath: "position")
        expandAnimation.fromValue = [0,sin(self.frame.width)]
        expandAnimation.toValue = [-self.frame.width,cos(self.frame.width)]
        expandAnimation.duration = animationDuration
        expandAnimation.fillMode = kCAFillModeForwards
        expandAnimation.isRemovedOnCompletion = false
        upCircleLayer.add(expandAnimation, forKey: nil)
}

}

TestView 文件代码:

class TestView: UIView {

let upCircleLayer = CAShapeLayer.init()
let downCircleLayer = CAShapeLayer.init()

var path1 = UIBezierPath.init()
var path2 = UIBezierPath.init()

var animationDirection : Bool = true

override init(frame: CGRect) {
    super.init(frame: frame)
    self.backgroundColor = UIColor.clear
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
}



var up1Circle: UIBezierPath {
    return UIBezierPath(arcCenter: CGPoint(x: self.frame.size.width/4, y: self.frame.size.height/2),
                        radius: self.frame.size.height/4,
                        startAngle: 180.0 * .pi/180.0,
                        endAngle: 0.0,
                        clockwise: true)
}
var down2Circle: UIBezierPath {
    return UIBezierPath(arcCenter: CGPoint(x: 3*self.frame.size.width/4, y: self.frame.size.height/2),
                        radius: self.frame.size.height/4,
                        startAngle: 180.0 * .pi/180.0,
                        endAngle: 0.0,
                        clockwise: false)
}

var up22Circle: UIBezierPath {
    return UIBezierPath(arcCenter: CGPoint(x: 3*self.frame.size.width/4, y: self.frame.size.height/2),
                        radius: self.frame.size.height/4,
                        startAngle: 180.0 * .pi/180.0,
                        endAngle: 0.0,
                        clockwise: true)
}
var down11Circle: UIBezierPath {
    return UIBezierPath(arcCenter: CGPoint(x: self.frame.size.width/4, y: self.frame.size.height/2),
                        radius: self.frame.size.height/4,
                        startAngle: 180.0 * .pi/180.0,
                        endAngle: 0.0,
                        clockwise: false)
}



var up2Circle: UIBezierPath {
    return UIBezierPath(arcCenter: CGPoint(x: 3*self.frame.size.width/4, y: self.frame.size.height/2),
                        radius: self.frame.size.height/4,
                        startAngle: 0.0,
                        endAngle: 180.0 * .pi/180.0,
                        clockwise: true)
}
var down1Circle: UIBezierPath {
    return UIBezierPath(arcCenter: CGPoint(x: self.frame.size.width/4, y: self.frame.size.height/2),
                        radius: self.frame.size.height/4,
                        startAngle: 0.0,
                        endAngle: 180.0 * .pi/180.0,
                        clockwise: false)
}

func addAnimationLayer() {

    path1.append(up1Circle);
    path1.append(down2Circle);

    path2.append(down11Circle)
    path2.append(up22Circle)



    // Add Upper Circle Layer
    upCircleLayer.fillColor = UIColor.clear.cgColor
    upCircleLayer.strokeColor = UIColor.black.cgColor
    upCircleLayer.lineWidth = 8.0
    upCircleLayer.path = path1.cgPath
    layer.addSublayer(upCircleLayer)


     Timer.scheduledTimer(timeInterval: 2.0, target: self, selector: #selector(expand1), userInfo: nil, repeats: true)

}

func expand() {

    if animationDirection{
        //upCircleLayer.path = path1.cgPath
        let expandAnimation: CABasicAnimation = CABasicAnimation(keyPath: "path")
        expandAnimation.fromValue = path1.cgPath
        expandAnimation.toValue = path2.cgPath
        expandAnimation.duration = 1.5
        //expandAnimation.fillMode = kCAFillModeForwards
        expandAnimation.isRemovedOnCompletion = false
        upCircleLayer.add(expandAnimation, forKey: nil)

        animationDirection = false
    }
    else{
        //upCircleLayer.path = path2.cgPath
        let expandAnimation: CABasicAnimation = CABasicAnimation(keyPath: "path")
        expandAnimation.fromValue = path2.cgPath
        expandAnimation.toValue = path1.cgPath
        expandAnimation.duration = 1.5
        //expandAnimation.fillMode = kCAFillModeForwards
        expandAnimation.isRemovedOnCompletion = false
        upCircleLayer.add(expandAnimation, forKey: nil)
        animationDirection = true
    }


}


func expand1() {

    let expandAnimation: CABasicAnimation = CABasicAnimation(keyPath: "position")
    expandAnimation.fromValue = [0,self.frame.height/2]
    expandAnimation.toValue = 500
    expandAnimation.duration = 2.0
    expandAnimation.fillMode = kCAFillModeForwards
    expandAnimation.isRemovedOnCompletion = false
    upCircleLayer.add(expandAnimation, forKey: nil)
}

}

TestView1.swift 文件的代码

类 TestView1: UIView {

let animationLayer = CAShapeLayer.init()

var path1 = UIBezierPath.init()
var path2 = UIBezierPath.init()
var path = UIBezierPath.init()
var circleRadius : CGFloat = 26.0;
var centerLineHeight : CGFloat = 40.0
var animationDuration : Double = 2.0

var animationDirection : Bool = true

override init(frame: CGRect) {
    super.init(frame: frame)
    self.backgroundColor = UIColor.black
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
}


var centerMainLine: UIBezierPath {
    let frameSize = self.frame.size
    let centerLine = UIBezierPath()
    centerLine.move(to: CGPoint(x: frameSize.width/2, y: frameSize.height/2 - centerLineHeight/2))
    centerLine.addLine(to: CGPoint(x: frameSize.width/2, y: frameSize.height/2 + centerLineHeight/2))
    return centerLine
}

var upLeftCircle: UIBezierPath {
    let frameSize = self.frame.size
    let halfCircle = UIBezierPath(arcCenter: CGPoint(x: frameSize.width/2 - circleRadius, y: frameSize.height/2 - centerLineHeight/2),
                        radius: circleRadius,
                        startAngle: 180.0 * .pi/180.0,
                        endAngle: 0.0,
                        clockwise: true)
    return halfCircle
}
var upRightCircle: UIBezierPath {
    let frameSize = self.frame.size
    let halfCircle = UIBezierPath(arcCenter: CGPoint(x: frameSize.width/2 + circleRadius, y: frameSize.height/2 - centerLineHeight/2),
                                  radius: circleRadius,
                                  startAngle: 180.0 * .pi/180.0,
                                  endAngle: 0.0,
                                  clockwise: true)
    return halfCircle
}
var downLeftCircle: UIBezierPath {
    let frameSize = self.frame.size
    let halfCircle = UIBezierPath(arcCenter: CGPoint(x: frameSize.width/2 - circleRadius, y: frameSize.height/2 + centerLineHeight/2),
                                  radius: circleRadius,
                                  startAngle: 180.0 * .pi/180.0,
                                  endAngle: 0.0,
                                  clockwise: false)
    return halfCircle
}
var downRightCircle: UIBezierPath {
    let frameSize = self.frame.size
    let halfCircle = UIBezierPath(arcCenter: CGPoint(x: frameSize.width/2 + circleRadius, y: frameSize.height/2 + centerLineHeight/2),
                                  radius: circleRadius,
                                  startAngle: 180.0 * .pi/180.0,
                                  endAngle: 0.0,
                                  clockwise: false)
    return halfCircle
}

func drawUpCircle(centerPoint:CGPoint, radiusValue:CGFloat) -> UIBezierPath {

    let halfCircle = UIBezierPath(arcCenter: centerPoint,
                                  radius: radiusValue,
                                  startAngle: 180.0 * .pi/180.0,
                                  endAngle: 0.0,
                                  clockwise: true)
    return halfCircle
}
func drawDownCircle(centerPoint:CGPoint,radiusValue:CGFloat) -> UIBezierPath {

    let halfCircle = UIBezierPath(arcCenter: centerPoint,
                                  radius: radiusValue,
                                  startAngle: 180.0 * .pi/180.0,
                                  endAngle: 0.0,
                                  clockwise: false)
    return halfCircle
}
func drawLine(fromPoint:CGPoint,toPoint:CGPoint) -> UIBezierPath {

    let line = UIBezierPath()
    line.move(to: fromPoint)
    line.addLine(to: toPoint)
    return line
}


func addAnimationLayer() {

    createPathOne()
    createPathTwo()
    createPath()

    // set Animation Layer design
    animationLayer.fillColor = UIColor.clear.cgColor
    animationLayer.strokeColor = UIColor.white.cgColor
    animationLayer.lineWidth = 8.0
    animationLayer.path = path.cgPath
    layer.addSublayer(animationLayer)
    expand1()
    Timer.scheduledTimer(timeInterval: 10.0, target: self, selector: #selector(expand1), userInfo: nil, repeats: true)
}
func expand1() {

    let expandAnimation: CABasicAnimation = CABasicAnimation(keyPath: "position")
    expandAnimation.fromValue = [0,0]
    expandAnimation.toValue = [-2000,0]
    expandAnimation.duration = 10.0
    expandAnimation.fillMode = kCAFillModeForwards
    expandAnimation.isRemovedOnCompletion = false
    animationLayer.add(expandAnimation, forKey: nil)
}
func expand() {
    animationLayer.path = centerMainLine.cgPath
    if animationDirection{

        let expandAnimation: CABasicAnimation = CABasicAnimation(keyPath: "path")
        expandAnimation.fromValue = path1.cgPath
        expandAnimation.toValue = path2.cgPath
        expandAnimation.duration = animationDuration
        expandAnimation.fillMode = kCAFillModeBackwards
        expandAnimation.isRemovedOnCompletion = false
        animationLayer.add(expandAnimation, forKey: nil)

        animationDirection = false
    }
    else{
        let expandAnimation: CABasicAnimation = CABasicAnimation(keyPath: "path")
        expandAnimation.fromValue = path2.cgPath
        expandAnimation.toValue = path1.cgPath
        expandAnimation.duration = animationDuration
        expandAnimation.fillMode = kCAFillModeForwards
        expandAnimation.isRemovedOnCompletion = false
        animationLayer.add(expandAnimation, forKey: nil)

        animationDirection = true
    }
}

func createPathOne(){

    path1.append(upLeftCircle);
    path1.append(centerMainLine);
    path1.append(downRightCircle)

}
func createPathTwo(){
    path2.append(downLeftCircle);
    path2.append(centerMainLine);
    path2.append(upRightCircle)
}

func createPath()  {
    let frameSize = self.frame.size;

    let lineHeight1 : CGFloat = 30
    let lineHeight2 : CGFloat = 20

    let radius1 : CGFloat = 40.0
    let radius2 : CGFloat = 20.0

    var lastPoint : CGPoint = CGPoint(x:0.0,y:frameSize.height/2 - lineHeight1/2)
    for i in 1...10{

        let p1 = drawUpCircle(centerPoint: CGPoint(x: lastPoint.x + radius1, y: lastPoint.y  ), radiusValue: radius1)
        lastPoint = p1.currentPoint;

        let p2 = drawLine(fromPoint: lastPoint , toPoint: CGPoint(x:lastPoint.x, y: lastPoint.y+lineHeight1))
        lastPoint = p2.currentPoint;

        let p3 = drawDownCircle(centerPoint: CGPoint(x:lastPoint.x + radius1, y: lastPoint.y), radiusValue: radius1)
        lastPoint = p3.currentPoint;

        let p4 = drawLine(fromPoint: lastPoint, toPoint: CGPoint(x:lastPoint.x, y: lastPoint.y - lineHeight2))
        lastPoint = p4.currentPoint;

        let p5 = drawUpCircle(centerPoint: CGPoint(x:lastPoint.x + radius2, y: lastPoint.y), radiusValue: radius2)
        lastPoint = p5.currentPoint;

        let p6 = drawLine(fromPoint: lastPoint, toPoint: CGPoint(x:lastPoint.x, y: lastPoint.y + lineHeight2))
        lastPoint = p6.currentPoint;

        let p7 = drawDownCircle(centerPoint: CGPoint(x:lastPoint.x + radius2, y: lastPoint.y), radiusValue: radius2)
        lastPoint = p7.currentPoint

        let p8  = drawLine(fromPoint: lastPoint, toPoint: CGPoint(x:lastPoint.x, y: lastPoint.y - lineHeight1))
        lastPoint = p8.currentPoint;

        path.append(p1)
        path.append(p2)
        path.append(p3)
        path.append(p4)
        path.append(p5)
        path.append(p6)
        path.append(p7)
        path.append(p8)

    }



}

}

现在运行代码来检查动画加载器。在viewcontroller的viewDidAppear方法中注释/取消注释其他2个loader方法。

享受!!

【讨论】:

    【解决方案3】:

    如果有人正在寻找 @rob mayoff 解决方案的 Objective C 版本

    在 SpinnerView.h 中

    #import <UIKit/UIKit.h>
    
    IB_DESIGNABLE
    @interface SpinnerView : UIView
    
    @end
    

    在 SpinnerView.m 中

    #import "SpinnerView.h"
    #import "Pose.h"
    
    
    @implementation SpinnerView
    
    
    - (instancetype) initWithFrame:(CGRect)frame{
        self = [super initWithFrame:frame];
        return self;
    }
    
    - (instancetype) initWithCoder:(NSCoder *)aDecoder{
        self = [super initWithCoder:aDecoder];
        return self;
    }
    
    - (CAShapeLayer*) layer {
        return (CAShapeLayer*)super.layer;
    }
    
    - (CAShapeLayer*) getLayer{
        return (CAShapeLayer*)super.layer;
    }
    
    + (Class)layerClass{
        return [CAShapeLayer class];
    }
    
    
    - (void) layoutSubviews{
        [super layoutSubviews];
        [self getLayer].fillColor = nil;
        [self getLayer].strokeColor = [UIColor blackColor].CGColor;
        [self getLayer].lineWidth = 3;
        [self setPath];
    }
    
    - (void) didMoveToWindow{
        [self animate];
    }
    
    - (void) setPath{
        UIBezierPath* bezierPath = ([UIBezierPath bezierPathWithOvalInRect:CGRectInset(self.bounds,  [self getLayer].lineWidth/2,  [self getLayer].lineWidth/2)]);
        [self getLayer].path = bezierPath.CGPath;
    }
    
    
    - (NSArray*) poses{
        NSMutableArray* poses = [[NSMutableArray alloc] init];
        [poses addObject:[[Pose alloc] initWith:0.0 start:0.000 length:0.7]];
        [poses addObject:[[Pose alloc] initWith:0.6 start:0.500 length:0.5]];
        [poses addObject:[[Pose alloc] initWith:0.6 start:1.000 length:0.3]];
        [poses addObject:[[Pose alloc] initWith:0.6 start:1.500 length:0.1]];
        [poses addObject:[[Pose alloc] initWith:0.2 start:1.875 length:0.1]];
        [poses addObject:[[Pose alloc] initWith:0.2 start:2.250 length:0.3]];
        [poses addObject:[[Pose alloc] initWith:0.2 start:2.625 length:0.7]];
        [poses addObject:[[Pose alloc] initWith:0.2 start:3.000 length:0.5]];
    
        return poses;
    }
    
    - (void) animate{
        CFTimeInterval time = 0;
        NSMutableArray* times = [NSMutableArray new];;
        CGFloat start = 0;
        NSMutableArray* rotations = [NSMutableArray new];
        NSMutableArray* strokeEnds  = [NSMutableArray new];
    
        NSArray* posses = [self poses];
        double totalSeconds = [[posses valueForKeyPath:@"@sum.secondsSincePriorPose"] doubleValue];
    
        for(Pose* pose in posses){
            time += pose.secondsSincePriorPose;
            [times addObject:[NSNumber numberWithDouble:time/totalSeconds]];
            start = pose.start;
            [rotations addObject:[NSNumber numberWithDouble:start*2*M_PI]];
            [strokeEnds addObject:[NSNumber numberWithDouble:pose.length]];
        }
    
        [times addObject:[times lastObject]];
        [rotations addObject:[rotations firstObject]];
        [strokeEnds addObject:[strokeEnds firstObject]];
    
        [self animateKeyPath:@"strokeEnd" duration:totalSeconds times:times values:strokeEnds];
        [self animateKeyPath:@"transform.rotation" duration:totalSeconds times:times values:rotations];
    
        [self animateStrokeHueWithDuration:totalSeconds * 5];
    }
    
    
    - (void) animateKeyPath:(NSString*)keyPath duration:(CFTimeInterval)duration times:(NSArray*)times values:(NSArray*)values{
        CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath:keyPath];
        animation.keyTimes = times;
        animation.values = values;
        animation.calculationMode = kCAAnimationLinear;
        animation.duration = duration;
        animation.repeatCount = FLT_MAX;
        [[self getLayer] addAnimation:animation forKey:animation.keyPath];
    }
    
    
    - (void) animateStrokeHueWithDuration:(CFTimeInterval)duration{
    
        CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath:@"strokeColor"];
        NSMutableArray *keyTimes = [NSMutableArray array];
        NSMutableArray *values = [NSMutableArray array];
    
        for (NSInteger i = 0; i < 36; i++) {
            [keyTimes addObject: [NSNumber numberWithDouble:(CFTimeInterval)i/(CFTimeInterval)36]];
            [values addObject:(id)[UIColor colorWithHue:(CGFloat)i/(CGFloat)36 saturation:1 brightness:1 alpha:1].CGColor];
        }
    
        animation.keyTimes = keyTimes;
        animation.values = values;
        animation.calculationMode = kCAAnimationLinear;
        animation.duration = duration;
        animation.repeatCount = FLT_MAX;
        [[self getLayer] addAnimation:animation forKey:animation.keyPath];
    
    }
    
    
    @end
    

    姿势.h

    #import <Foundation/Foundation.h>
    #import <UIKit/UIKit.h>
    
    @interface Pose : NSObject
    
    @property CFTimeInterval secondsSincePriorPose;
    @property CGFloat start;
    @property CGFloat length;
    
    - (instancetype) initWith:(CFTimeInterval)timeInterval start:(CGFloat)start length:(CGFloat)length;
    
    @end
    

    姿势.m

    #import "Pose.h"
    #import <UIKit/UIKit.h>
    
    @implementation Pose 
    
    - (instancetype) initWith:(CFTimeInterval)timeInterval start:(CGFloat)start length:(CGFloat)length{
        self = [super init];
        self.start = start;
        self.length = length;
        self.secondsSincePriorPose = timeInterval;
        return self;
    }
    
    
    @end
    

    【讨论】:

    • 我们可以调用和使用它的任何示例用法吗?
    猜你喜欢
    • 1970-01-01
    • 2018-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-25
    • 1970-01-01
    • 2013-09-06
    • 2019-03-03
    相关资源
    最近更新 更多