animation绘制一个圆圈

我正在寻找一种方法来创build一个圆圈的绘图animation。 我已经能够创造出这个圈子,但它一起吸引了所有人。

这是我的CircleView类:

 import UIKit class CircleView: UIView { override init(frame: CGRect) { super.init(frame: frame) self.backgroundColor = UIColor.clearColor() } required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func drawRect(rect: CGRect) { // Get the Graphics Context var context = UIGraphicsGetCurrentContext(); // Set the circle outerline-width CGContextSetLineWidth(context, 5.0); // Set the circle outerline-colour UIColor.redColor().set() // Create Circle CGContextAddArc(context, (frame.size.width)/2, frame.size.height/2, (frame.size.width - 10)/2, 0.0, CGFloat(M_PI * 2.0), 1) // Draw CGContextStrokePath(context); } } 

这里是我如何将它添加到我的视图控制器中的视图层次结构:

 func addCircleView() { let diceRoll = CGFloat(Int(arc4random_uniform(7))*50) var circleWidth = CGFloat(200) var circleHeight = circleWidth // Create a new CircleView var circleView = CircleView(frame: CGRectMake(diceRoll, 0, circleWidth, circleHeight)) view.addSubview(circleView) } 

有没有一种方法来animation1秒钟的圆形图画?

例如,通过animation的一部分,它将看起来像这个图像中的蓝线:

部分动画

最简单的方法是使用核心animation的强大function为您完成大部分工作。 要做到这一点,我们必须将您的圆形绘制代码从drawRect函数移动到CAShapeLayer 。 然后,我们可以使用一个CABasicAnimation来animationCAShapeLayerstrokeEnd属性从0.01.0strokeEnd是这里魔术的重要组成部分; 从文档:

结合strokeStart属性,此属性定义中风的path的子区域。 此属性中的值指示strokeStart属性定义起点时沿着完成描边的path的相对点。 值为0.0表示path的开始,而值为1.0表示path的结束。 中间的值沿path长度线性解释。

如果我们将strokeEnd设置为0.0 ,它将不会绘制任何东西。 如果我们把它设置为1.0 ,它会画一个整圈。 如果我们将其设置为0.5 ,则会画半圈。 等等

因此,要开始,我们可以在CircleViewinit函数中创build一个CAShapeLayer ,并将该图层添加到视图的sublayers (也请确保删除drawRect函数,因为图层现在将绘制圆形):

 let circleLayer: CAShapeLayer! override init(frame: CGRect) { super.init(frame: frame) self.backgroundColor = UIColor.clearColor() // Use UIBezierPath as an easy way to create the CGPath for the layer. // The path should be the entire circle. let circlePath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0), radius: (frame.size.width - 10)/2, startAngle: 0.0, endAngle: CGFloat(M_PI * 2.0), clockwise: true) // Setup the CAShapeLayer with the path, colors, and line width circleLayer = CAShapeLayer() circleLayer.path = circlePath.CGPath circleLayer.fillColor = UIColor.clearColor().CGColor circleLayer.strokeColor = UIColor.redColor().CGColor circleLayer.lineWidth = 5.0; // Don't draw the circle initially circleLayer.strokeEnd = 0.0 // Add the circleLayer to the view's layer's sublayers layer.addSublayer(circleLayer) } 

注意:我们正在设置circleLayer.strokeEnd = 0.0以便不会马上绘制圆。

现在,让我们添加一个我们可以调用的函数来触发圆形animation:

 func animateCircle(duration: NSTimeInterval) { // We want to animate the strokeEnd property of the circleLayer let animation = CABasicAnimation(keyPath: "strokeEnd") // Set the animation duration appropriately animation.duration = duration // Animate from 0 (no circle) to 1 (full circle) animation.fromValue = 0 animation.toValue = 1 // Do a linear animation (ie the speed of the animation stays the same) animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) // Set the circleLayer's strokeEnd property to 1.0 now so that it's the // right value when the animation ends. circleLayer.strokeEnd = 1.0 // Do the actual animation circleLayer.addAnimation(animation, forKey: "animateCircle") } 

然后,我们需要做的就是改变你的addCircleView函数,以便在将CircleView添加到其CircleView superview时触发animation:

 func addCircleView() { let diceRoll = CGFloat(Int(arc4random_uniform(7))*50) var circleWidth = CGFloat(200) var circleHeight = circleWidth // Create a new CircleView var circleView = CircleView(frame: CGRectMake(diceRoll, 0, circleWidth, circleHeight)) view.addSubview(circleView) // Animate the drawing of the circle over the course of 1 second circleView.animateCircle(1.0) } 

所有放在一起应该看起来像这样:

圈动画

注意:这样不会重复,animation之后它会保持整整一圈。

麦克斯答案更新为Swift 3.0

 var circleLayer: CAShapeLayer! override init(frame: CGRect) { super.init(frame: frame) self.backgroundColor = UIColor.clear // Use UIBezierPath as an easy way to create the CGPath for the layer. // The path should be the entire circle. let circlePath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0), radius: (frame.size.width - 10)/2, startAngle: 0.0, endAngle: CGFloat(M_PI * 2.0), clockwise: true) // Setup the CAShapeLayer with the path, colors, and line width circleLayer = CAShapeLayer() circleLayer.path = circlePath.cgPath circleLayer.fillColor = UIColor.clear.cgColor circleLayer.strokeColor = UIColor.red.cgColor circleLayer.lineWidth = 5.0; // Don't draw the circle initially circleLayer.strokeEnd = 0.0 // Add the circleLayer to the view's layer's sublayers layer.addSublayer(circleLayer) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func animateCircle(duration: TimeInterval) { // We want to animate the strokeEnd property of the circleLayer let animation = CABasicAnimation(keyPath: "strokeEnd") // Set the animation duration appropriately animation.duration = duration // Animate from 0 (no circle) to 1 (full circle) animation.fromValue = 0 animation.toValue = 1 // Do a linear animation (ie The speed of the animation stays the same) animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) // Set the circleLayer's strokeEnd property to 1.0 now so that it's the // Right value when the animation ends circleLayer.strokeEnd = 1.0 // Do the actual animation circleLayer.add(animation, forKey: "animateCircle") } 

调用函数:

 func addCircleView() { let diceRoll = CGFloat(Int(arc4random_uniform(7))*50) var circleWidth = CGFloat(200) var circleHeight = circleWidth // Create a new CircleView let circleView = CircleView(frame: CGRect(x: diceRoll, y: 0, width: circleWidth, height: circleHeight)) //let test = CircleView(frame: CGRect(x: diceRoll, y: 0, width: circleWidth, height: circleHeight)) view.addSubview(circleView) // Animate the drawing of the circle over the course of 1 second circleView.animateCircle(duration: 1.0) } 

迈克的回答非常好! 另一个不错的简单方法是使用drawRect和setNeedsDisplay()。 这看起来很迟钝,但它不是:-) 在这里输入图像描述

我们想要从顶部开始画一个圆圈,即-90°,结束于270°。 圆的中心是(centerX,centerY),具有给定的半径。 CurrentAngle是圆的终点的当前angular度,从minAngle(-90)到maxAngle(270)。

 // MARK: Properties let centerX:CGFloat = 55 let centerY:CGFloat = 55 let radius:CGFloat = 50 var currentAngle:Float = -90 let minAngle:Float = -90 let maxAngle:Float = 270 

在drawRect中,我们指定圆圈应该如何显示:

 override func drawRect(rect: CGRect) { let context = UIGraphicsGetCurrentContext() let path = CGPathCreateMutable() CGPathAddArc(path, nil, centerX, centerY, radius, CGFloat(GLKMathDegreesToRadians(minAngle)), CGFloat(GLKMathDegreesToRadians(currentAngle)), false) CGContextAddPath(context, path) CGContextSetStrokeColorWithColor(context, UIColor.blueColor().CGColor) CGContextSetLineWidth(context, 3) CGContextStrokePath(context) } 

现在的问题是,currentAngle没有改变,这个圆是静态的,甚至不显示为currentAngle = minAngle。

然后我们创build一个计时器,每当这个计时器触发时,我们增加currentAngle。 在class级的顶部,添加两次火灾之间的时间间隔:

 let timeBetweenDraw:CFTimeInterval = 0.01 

在你的init中,添加计时器:

 NSTimer.scheduledTimerWithTimeInterval(timeBetweenDraw, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true) 

我们可以添加定时器触发时要调用的函数:

 func updateTimer() { if currentAngle < maxAngle { currentAngle += 1 } } 

可悲的是,在运行应用程序时,没有显示,因为我们没有指定它应该再次绘制的系统。 这是通过调用setNeedsDisplay()来完成的。 这是更新的计时器function:

 func updateTimer() { if currentAngle < maxAngle { currentAngle += 1 setNeedsDisplay() } } 

_ _ _

所有你需要的代码总结在这里:

 import UIKit import GLKit class CircleClosing: UIView { // MARK: Properties let centerX:CGFloat = 55 let centerY:CGFloat = 55 let radius:CGFloat = 50 var currentAngle:Float = -90 let timeBetweenDraw:CFTimeInterval = 0.01 // MARK: Init required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) setup() } override init(frame: CGRect) { super.init(frame: frame) setup() } func setup() { self.backgroundColor = UIColor.clearColor() NSTimer.scheduledTimerWithTimeInterval(timeBetweenDraw, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true) } // MARK: Drawing func updateTimer() { if currentAngle < 270 { currentAngle += 1 setNeedsDisplay() } } override func drawRect(rect: CGRect) { let context = UIGraphicsGetCurrentContext() let path = CGPathCreateMutable() CGPathAddArc(path, nil, centerX, centerY, radius, -CGFloat(M_PI/2), CGFloat(GLKMathDegreesToRadians(currentAngle)), false) CGContextAddPath(context, path) CGContextSetStrokeColorWithColor(context, UIColor.blueColor().CGColor) CGContextSetLineWidth(context, 3) CGContextStrokePath(context) } } 

如果要更改速度,只需修改updateTimer函数或调用此函数的速率即可。 另外,一旦圆圈完成后,您可能想使计时器无效,而我忘记了:-)

注意:要在故事板中添加圆圈,只需添加一个视图,select它,转到其“ 标识”检查器 ,然后select“ 类” ,指定CircleClosing

干杯! BRO

如果你想要一个完成处理程序,这是另一个类似于由Mike S在Swift 3.0中完成的解决scheme

 func animateCircleFull(duration: TimeInterval) { CATransaction.begin() let animation = CABasicAnimation(keyPath: "strokeEnd") animation.duration = duration animation.fromValue = 0 animation.toValue = 1 animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) circleLayer.strokeEnd = 1.0 CATransaction.setCompletionBlock { print("animation complete") } // Do the actual animation circleLayer.add(animation, forKey: "animateCircle") CATransaction.commit() } 

使用完成处理程序,您可以再次运行animation,或者通过recursion调用相同的函数重新执行animation(这看起来不会很好),或者您可以有一个反转的函数,它会持续链接,直到满足条件, 例如:

 func animate(duration: TimeInterval){ self.isAnimating = true self.animateCircleFull(duration: 1) } func endAnimate(){ self.isAnimating = false } func animateCircleFull(duration: TimeInterval) { if self.isAnimating{ CATransaction.begin() let animation = CABasicAnimation(keyPath: "strokeEnd") animation.duration = duration animation.fromValue = 0 animation.toValue = 1 animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) circleLayer.strokeEnd = 1.0 CATransaction.setCompletionBlock { self.animateCircleEmpty(duration: duration) } // Do the actual animation circleLayer.add(animation, forKey: "animateCircle") CATransaction.commit() } } func animateCircleEmpty(duration: TimeInterval){ if self.isAnimating{ CATransaction.begin() let animation = CABasicAnimation(keyPath: "strokeEnd") animation.duration = duration animation.fromValue = 1 animation.toValue = 0 animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) circleLayer.strokeEnd = 0 CATransaction.setCompletionBlock { self.animateCircleFull(duration: duration) } // Do the actual animation circleLayer.add(animation, forKey: "animateCircle") CATransaction.commit() } } 

为了使它更有趣,你可以像这样改变animation的方向:

  func setCircleClockwise(){ let circlePath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0), radius: (frame.size.width - 10)/2, startAngle: 0.0, endAngle: CGFloat(M_PI * 2.0), clockwise: true) self.circleLayer.removeFromSuperlayer() self.circleLayer = formatCirle(circlePath: circlePath) self.layer.addSublayer(self.circleLayer) } func setCircleCounterClockwise(){ let circlePath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0), radius: (frame.size.width - 10)/2, startAngle: 0.0, endAngle: CGFloat(M_PI * 2.0), clockwise: false) self.circleLayer.removeFromSuperlayer() self.circleLayer = formatCirle(circlePath: circlePath) self.layer.addSublayer(self.circleLayer) } func formatCirle(circlePath: UIBezierPath) -> CAShapeLayer{ let circleShape = CAShapeLayer() circleShape.path = circlePath.cgPath circleShape.fillColor = UIColor.clear.cgColor circleShape.strokeColor = UIColor.red.cgColor circleShape.lineWidth = 10.0; circleShape.strokeEnd = 0.0 return circleShape } func animate(duration: TimeInterval){ self.isAnimating = true self.animateCircleFull(duration: 1) } func endAnimate(){ self.isAnimating = false } func animateCircleFull(duration: TimeInterval) { if self.isAnimating{ CATransaction.begin() let animation = CABasicAnimation(keyPath: "strokeEnd") animation.duration = duration animation.fromValue = 0 animation.toValue = 1 animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) circleLayer.strokeEnd = 1.0 CATransaction.setCompletionBlock { self.setCircleCounterClockwise() self.animateCircleEmpty(duration: duration) } // Do the actual animation circleLayer.add(animation, forKey: "animateCircle") CATransaction.commit() } } func animateCircleEmpty(duration: TimeInterval){ if self.isAnimating{ CATransaction.begin() let animation = CABasicAnimation(keyPath: "strokeEnd") animation.duration = duration animation.fromValue = 1 animation.toValue = 0 animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) circleLayer.strokeEnd = 0 CATransaction.setCompletionBlock { self.setCircleClockwise() self.animateCircleFull(duration: duration) } // Do the actual animation circleLayer.add(animation, forKey: "animateCircle") CATransaction.commit() } }