Swift: 从 UIView 按顺序动画图层
Swift: Animate layers in sequence from UIView
我已经向 UIView 添加了几个图层,现在希望按顺序为它们制作动画。
我有 5 个图层要制作动画,这些都是圆圈。我使用可观察字段来获取值变化的通知,并开始使用 needsDisplayForKey() 和 actionForKey() 方法制作动画。
可观察字段:
@NSManaged private var _startAngle: CGFloat
@NSManaged private var _endAngle: CGFloat
目前,当我更改每一层的一个可观察场时,所有层都会同时设置动画。我希望改变这一点,并希望在 2.5 秒的时间跨度内按顺序为所有这些设置动画。所以当一个动画完成后,我希望为下一层等制作动画
目前我知道动画已经完成的唯一方法是使用 animationDidStop() 方法。然而,通过这种方式我无法知道其他正在动画的图层。你们知道对此有什么好的解决方案吗?
我的图层:
class HourLayer: CAShapeLayer {
@NSManaged private var _startAngle: CGFloat
@NSManaged private var _endAngle: CGFloat
@NSManaged private var _fillColor: UIColor
@NSManaged private var _strokeWidth: CGFloat
@NSManaged private var _strokeColor: UIColor
@NSManaged private var _duration: CFTimeInterval
private var _previousEndAngle: CGFloat = 0.0
private var _previousStartAngle: CGFloat = 0.0
private let _lenghtOfArc: CGFloat = CGFloat(2 * M_PI)
internal var StartAngle: CGFloat {
get {
return _startAngle
}
set {
_startAngle = newValue
}
}
internal var EndAngle: CGFloat {
get {
return _endAngle
}
set {
_endAngle = newValue
}
}
internal var FillColor: UIColor {
get {
return _fillColor
}
set {
_fillColor = newValue
}
}
internal var StrokeWidth: CGFloat {
get {
return _strokeWidth
}
set {
_strokeWidth = newValue
}
}
internal var StrokeColor: UIColor {
get {
return _strokeColor
}
set {
_strokeColor = newValue
}
}
internal var DurationOfAnimation: CFTimeInterval {
get {
return _duration
}
set {
_duration = newValue
}
}
required override init!() {
super.init()
self._startAngle = 0.0
self._endAngle = 0.0
self._fillColor = UIColor.clearColor()
self._strokeWidth = 4.0
self._strokeColor = UIColor.blackColor()
self._duration = 1.0
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
fatalError("required init(:coder) is missing")
}
override init!(layer: AnyObject!) {
super.init(layer: layer)
if layer.isKindOfClass(HourLayer) {
var other: HourLayer = layer as HourLayer
self._startAngle = other._startAngle
self._endAngle = other._endAngle
self._fillColor = other._fillColor
self._strokeWidth = other._strokeWidth
self._strokeColor = other._strokeColor
self._duration = other._duration
}
}
override var frame: CGRect {
didSet {
self.setNeedsLayout()
self.setNeedsDisplay()
}
}
override func actionForKey(event: String!) -> CAAction! {
if event == "_startAngle" || event == "_endAngle" {
return makeAnimationForKey(event)
}
return super.actionForKey(event)
}
private func makeAnimationForKey(event: String) -> CABasicAnimation {
// We want to animate the strokeEnd property of the circleLayer
let animation: CABasicAnimation = CABasicAnimation(keyPath: event)
// Set the animation duration appropriately
animation.duration = self._duration
// When no animation has been triggered and the presentation layer does not exist yet.
if self.presentationLayer() == nil {
if event == "_startAngle" {
animation.fromValue = self._previousStartAngle
self._previousStartAngle = self._startAngle
} else if event == "_endAngle" {
animation.fromValue = self._previousEndAngle
self._previousEndAngle = self._endAngle
}
} else {
animation.fromValue = self.presentationLayer()!.valueForKey(event)
}
animation.removedOnCompletion = false
animation.delegate = self
// Do a linear animation (i.e. the speed of the animation stays the same)
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
return animation
}
override class func needsDisplayForKey(key: String) -> Bool {
if key == "_startAngle" || key == "_endAngle" {
return true
}
return super.needsDisplayForKey(key)
}
override func drawInContext(ctx: CGContext!) {
var center: CGPoint = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2)
var radius: CGFloat = CGFloat((CGFloat(self.bounds.width) - CGFloat(_strokeWidth)) / 2)
// Set the stroke color
CGContextSetStrokeColorWithColor(ctx, _strokeColor.CGColor)
// Set the line width
CGContextSetLineWidth(ctx, _strokeWidth)
// Set the fill color (if you are filling the circle)
CGContextSetFillColorWithColor(ctx, UIColor.clearColor().CGColor)
// Draw the arc around the circle
CGContextAddArc(ctx, center.x, center.y, radius, _startAngle, _lenghtOfArc * _endAngle, 0)
// Draw the arc
CGContextDrawPath(ctx, kCGPathStroke) // or kCGPathFillStroke to fill and stroke the circle
super.drawInContext(ctx)
}
override func animationDidStop(anim: CAAnimation!, finished flag: Bool) {
// Trigger animation for other layer by setting their values for _startAngle and _endAngle.
}
}
我的解决方案在objective-c,你需要为swift采纳这个。试一试:-
创建一个 NSOperationQueue 并将动画调用添加到该队列。设置setMaxConcurrentOperationCount = 1
保证顺序执行。
NSOperationQueue *animQueue = [[NSOperationQueue alloc] init];
[animQueue setMaxConcurrentOperationCount:1];
现在从函数actionForKey
而不是调用动画函数,将其添加到操作队列
NSBlockOperation *animOperation = [[NSBlockOperation alloc] init];
__weak NSBlockOperation *weakOp = animOperation;
[weakOp addExecutionBlock:^{
[self makeAnimationForKey:event)]
}];
[animQueue addOperation:animOperation];
我还没有测试过这个,但我认为它应该可以。
试试这个..
UIview.animateWithDuration(2.0, delay 2.0, options: .CurveEaseIn, animations: {
//enter item to be animated here
}, completion: nil)
If you want to chain two animations together so that one starts when the other finishes, do not use animation notifications. Instead, use the beginTime property of your animation objects to start each one at the desired time. To chain two animations together, set the start time of the second animation to the end time of the first animation. For more information about animation and timing values, see Customizing the Timing of an Animation.
我有一些东西可以用,但我觉得它不是最理想的解决方案。我宁愿认为通知是一种更好的方式,但是嘿,如果苹果说得对的话....
我也必须设置:animation.fillMode = kCAFillModeBackwards
否则模型层会使圆圈在动画之前达到其最终状态,然后才会触发动画。
编辑
好的,所以我发现为我的动画使用多层很慢(我为每个视图使用了多个层,所以我想为一个圆圈制作动画,我有大约 6 个圆圈。这样做 8 次非常慢(6x8=48 个动画)。在一层中对所有内容进行动画处理要快得多。
我已经向 UIView 添加了几个图层,现在希望按顺序为它们制作动画。
我有 5 个图层要制作动画,这些都是圆圈。我使用可观察字段来获取值变化的通知,并开始使用 needsDisplayForKey() 和 actionForKey() 方法制作动画。
可观察字段:
@NSManaged private var _startAngle: CGFloat
@NSManaged private var _endAngle: CGFloat
目前,当我更改每一层的一个可观察场时,所有层都会同时设置动画。我希望改变这一点,并希望在 2.5 秒的时间跨度内按顺序为所有这些设置动画。所以当一个动画完成后,我希望为下一层等制作动画
目前我知道动画已经完成的唯一方法是使用 animationDidStop() 方法。然而,通过这种方式我无法知道其他正在动画的图层。你们知道对此有什么好的解决方案吗?
我的图层:
class HourLayer: CAShapeLayer {
@NSManaged private var _startAngle: CGFloat
@NSManaged private var _endAngle: CGFloat
@NSManaged private var _fillColor: UIColor
@NSManaged private var _strokeWidth: CGFloat
@NSManaged private var _strokeColor: UIColor
@NSManaged private var _duration: CFTimeInterval
private var _previousEndAngle: CGFloat = 0.0
private var _previousStartAngle: CGFloat = 0.0
private let _lenghtOfArc: CGFloat = CGFloat(2 * M_PI)
internal var StartAngle: CGFloat {
get {
return _startAngle
}
set {
_startAngle = newValue
}
}
internal var EndAngle: CGFloat {
get {
return _endAngle
}
set {
_endAngle = newValue
}
}
internal var FillColor: UIColor {
get {
return _fillColor
}
set {
_fillColor = newValue
}
}
internal var StrokeWidth: CGFloat {
get {
return _strokeWidth
}
set {
_strokeWidth = newValue
}
}
internal var StrokeColor: UIColor {
get {
return _strokeColor
}
set {
_strokeColor = newValue
}
}
internal var DurationOfAnimation: CFTimeInterval {
get {
return _duration
}
set {
_duration = newValue
}
}
required override init!() {
super.init()
self._startAngle = 0.0
self._endAngle = 0.0
self._fillColor = UIColor.clearColor()
self._strokeWidth = 4.0
self._strokeColor = UIColor.blackColor()
self._duration = 1.0
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
fatalError("required init(:coder) is missing")
}
override init!(layer: AnyObject!) {
super.init(layer: layer)
if layer.isKindOfClass(HourLayer) {
var other: HourLayer = layer as HourLayer
self._startAngle = other._startAngle
self._endAngle = other._endAngle
self._fillColor = other._fillColor
self._strokeWidth = other._strokeWidth
self._strokeColor = other._strokeColor
self._duration = other._duration
}
}
override var frame: CGRect {
didSet {
self.setNeedsLayout()
self.setNeedsDisplay()
}
}
override func actionForKey(event: String!) -> CAAction! {
if event == "_startAngle" || event == "_endAngle" {
return makeAnimationForKey(event)
}
return super.actionForKey(event)
}
private func makeAnimationForKey(event: String) -> CABasicAnimation {
// We want to animate the strokeEnd property of the circleLayer
let animation: CABasicAnimation = CABasicAnimation(keyPath: event)
// Set the animation duration appropriately
animation.duration = self._duration
// When no animation has been triggered and the presentation layer does not exist yet.
if self.presentationLayer() == nil {
if event == "_startAngle" {
animation.fromValue = self._previousStartAngle
self._previousStartAngle = self._startAngle
} else if event == "_endAngle" {
animation.fromValue = self._previousEndAngle
self._previousEndAngle = self._endAngle
}
} else {
animation.fromValue = self.presentationLayer()!.valueForKey(event)
}
animation.removedOnCompletion = false
animation.delegate = self
// Do a linear animation (i.e. the speed of the animation stays the same)
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
return animation
}
override class func needsDisplayForKey(key: String) -> Bool {
if key == "_startAngle" || key == "_endAngle" {
return true
}
return super.needsDisplayForKey(key)
}
override func drawInContext(ctx: CGContext!) {
var center: CGPoint = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2)
var radius: CGFloat = CGFloat((CGFloat(self.bounds.width) - CGFloat(_strokeWidth)) / 2)
// Set the stroke color
CGContextSetStrokeColorWithColor(ctx, _strokeColor.CGColor)
// Set the line width
CGContextSetLineWidth(ctx, _strokeWidth)
// Set the fill color (if you are filling the circle)
CGContextSetFillColorWithColor(ctx, UIColor.clearColor().CGColor)
// Draw the arc around the circle
CGContextAddArc(ctx, center.x, center.y, radius, _startAngle, _lenghtOfArc * _endAngle, 0)
// Draw the arc
CGContextDrawPath(ctx, kCGPathStroke) // or kCGPathFillStroke to fill and stroke the circle
super.drawInContext(ctx)
}
override func animationDidStop(anim: CAAnimation!, finished flag: Bool) {
// Trigger animation for other layer by setting their values for _startAngle and _endAngle.
}
}
我的解决方案在objective-c,你需要为swift采纳这个。试一试:-
创建一个 NSOperationQueue 并将动画调用添加到该队列。设置setMaxConcurrentOperationCount = 1
保证顺序执行。
NSOperationQueue *animQueue = [[NSOperationQueue alloc] init];
[animQueue setMaxConcurrentOperationCount:1];
现在从函数actionForKey
而不是调用动画函数,将其添加到操作队列
NSBlockOperation *animOperation = [[NSBlockOperation alloc] init];
__weak NSBlockOperation *weakOp = animOperation;
[weakOp addExecutionBlock:^{
[self makeAnimationForKey:event)]
}];
[animQueue addOperation:animOperation];
我还没有测试过这个,但我认为它应该可以。
试试这个..
UIview.animateWithDuration(2.0, delay 2.0, options: .CurveEaseIn, animations: {
//enter item to be animated here
}, completion: nil)
If you want to chain two animations together so that one starts when the other finishes, do not use animation notifications. Instead, use the beginTime property of your animation objects to start each one at the desired time. To chain two animations together, set the start time of the second animation to the end time of the first animation. For more information about animation and timing values, see Customizing the Timing of an Animation.
我有一些东西可以用,但我觉得它不是最理想的解决方案。我宁愿认为通知是一种更好的方式,但是嘿,如果苹果说得对的话....
我也必须设置:animation.fillMode = kCAFillModeBackwards 否则模型层会使圆圈在动画之前达到其最终状态,然后才会触发动画。
编辑
好的,所以我发现为我的动画使用多层很慢(我为每个视图使用了多个层,所以我想为一个圆圈制作动画,我有大约 6 个圆圈。这样做 8 次非常慢(6x8=48 个动画)。在一层中对所有内容进行动画处理要快得多。