Анимировать рисунок из окружности
Я ищу способ оживить рисунок круга. Я смог создать круг, но он объединяет все это вместе.
вот мой 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)
}
есть ли способ оживить рисунок круга в течение 1 секунды?
пример, часть пути через анимацию это будет выглядеть что-то вроде синей линии в этом изображение:
4 ответа:
самый простой способ сделать это-использовать силу основной анимации, чтобы сделать большую часть работы для вас. Для этого нам придется переместить ваш код рисования круга из вашего до
CAShapeLayer
. Тогда мы можем использоватьCABasicAnimation
анимацияCAShapeLayer
' sstrokeEnd
собственность от0.0
до1.0
.strokeEnd
это большая часть магии здесь; из документов:в сочетании с свойством strokeStart, это свойство определяет субрегион пути к инсульту. Значение в этом свойстве указывает относительная точка вдоль пути, в которой нужно закончить поглаживание в то время как свойство strokeStart определяет начальную точку. Значение 0.0 представляет собой начало пути, в то время как значение 1.0 представляет конец пути. Значения между ними интерпретируются линейно вдоль длина пути.
если мы ставим
strokeEnd
до0.0
, он ничего не нарисует. Если мы установим его в1.0
, это нарисует полный круг. Если мы установим его в0.5
, он нарисует полукруг. так далее.Итак, для начала, давайте создадим
CAShapeLayer
в своемCircleView
' s так что он запускает анимацию при добавленииCircleView
в своемsuperview
: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) }
все это вместе должно выглядеть примерно так:
Примечание: он не будет повторяться так, он останется полным кругом после того, как он оживет.
ответ микрофонов обновлен для 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 (i.e 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-это текущий угол конечной точки круга, идущий от 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.
затем мы создаем таймер, и всякий раз, когда этот таймер срабатывает, мы увеличиваем currentAngle. В верхней части вашего класса, добавьте время между двумя пожарами:
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(). Вот обновленная функция таймера :
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, или скорость, с которой эта функция вызывается. Кроме того, вы возможно, вы захотите аннулировать таймер после завершения круга, что я забыл сделать : -)
NB: чтобы добавить круг в раскадровке, просто добавьте представление, выберите его, перейдите к его Identity Inspector, а как класс, указать CircleClosing.
Ура! брат
Если вы хотите обработчик завершения, это еще одно решение, подобное тому, которое Майк S, сделано в Swift 3.0
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() }
С помощью обработчика завершения вы можете снова запустить анимацию, рекурсивно вызвав ту же функцию, чтобы сделать анимацию снова (что будет выглядеть не очень красиво), или у вас может быть обратная функция, которая будет непрерывно цепляться до тех пор, пока не будет выполнено условие, например:
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() } }
чтобы сделать его еще красивее, вы можете изменить направление из анимации, как это:
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() } }