核心animation进度callback
有一个简单的方法可以在核心animation运行时达到特定点(例如完成的50%和66%?
我正在考虑设置一个NSTimer,但是这并不是我想要的那么准确。
我终于为这个问题开发了一个解决scheme。
本质上,我希望每一帧都被callback,并且做我需要做的事情。
观察animation的进度没有明显的方法,但它实际上是可能的:
-
首先,我们需要创build一个名为“progress”的animation属性的CALayer的新子类。
-
我们将图层添加到我们的树中,然后创build一个animation,在animation的持续时间内将进度值从0驱动到1。
-
由于我们的progress属性可以是animation的,所以在我们的子类上为animation的每一帧调用drawInContext。 这个函数不需要重绘任何东西,但是它可以用来调用委托函数:)
这是类接口:
@protocol TAProgressLayerProtocol <NSObject> - (void)progressUpdatedTo:(CGFloat)progress; @end @interface TAProgressLayer : CALayer @property CGFloat progress; @property (weak) id<TAProgressLayerProtocol> delegate; @end
并执行:
@implementation TAProgressLayer // We must copy across our custom properties since Core Animation makes a copy // of the layer that it's animating. - (id)initWithLayer:(id)layer { self = [super initWithLayer:layer]; if (self) { TAProgressLayer *otherLayer = (TAProgressLayer *)layer; self.progress = otherLayer.progress; self.delegate = otherLayer.delegate; } return self; } // Override needsDisplayForKey so that we can define progress as being animatable. + (BOOL)needsDisplayForKey:(NSString*)key { if ([key isEqualToString:@"progress"]) { return YES; } else { return [super needsDisplayForKey:key]; } } // Call our callback - (void)drawInContext:(CGContextRef)ctx { if (self.delegate) { [self.delegate progressUpdatedTo:self.progress]; } } @end
然后,我们可以将图层添加到我们的主图层:
TAProgressLayer *progressLayer = [TAProgressLayer layer]; progressLayer.frame = CGRectMake(0, -1, 1, 1); progressLayer.delegate = self; [_sceneView.layer addSublayer:progressLayer];
并与其他animation一起animation:
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"progress"]; anim.duration = 4.0; anim.beginTime = 0; anim.fromValue = @0; anim.toValue = @1; anim.fillMode = kCAFillModeForwards; anim.removedOnCompletion = NO; [progressLayer addAnimation:anim forKey:@"progress"];
最后,随着animation的进展,委托人将被回叫:
- (void)progressUpdatedTo:(CGFloat)progress { // Do whatever you need to do... }
如果你不想破解CALayer来向你报告进度,还有另一种方法。 从概念上讲,您可以使用CADisplayLink来保证每一帧的callback,然后简单地测量animation开始以来的时间除以持续时间,以确定完成百分比。
开放源代码库INTUAnimationEngine将这个function打包成一个非常干净的API,看起来几乎和UIView基于块的animation一样:
// INTUAnimationEngine.h // ... + (NSInteger)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay animations:(void (^)(CGFloat percentage))animations completion:(void (^)(BOOL finished))completion; // ...
您只需要在启动其他animation的同时调用此方法,并传递相同的值以delay
duration
delay
,然后对于animation的每个帧,将以当前百分比完成animations
块的执行。 如果你想让你放心,你的时间是完全同步的,你可以从INTUAnimationEngine驱动你的animation。
我在接受的答案中提供了一个由tarmesbuild议的CALayer子类的Swift(2.0)实现:
protocol TAProgressLayerProtocol { func progressUpdated(progress: CGFloat) } class TAProgressLayer : CALayer { // MARK: - Progress-related properties var progress: CGFloat = 0.0 var progressDelegate: TAProgressLayerProtocol? = nil // MARK: - Initialization & Encoding // We must copy across our custom properties since Core Animation makes a copy // of the layer that it's animating. override init(layer: AnyObject) { super.init(layer: layer) if let other = layer as? TAProgressLayerProtocol { self.progress = other.progress self.progressDelegate = other.progressDelegate } } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) progressDelegate = aDecoder.decodeObjectForKey("progressDelegate") as? CALayerProgressProtocol progress = CGFloat(aDecoder.decodeFloatForKey("progress")) } override func encodeWithCoder(aCoder: NSCoder) { super.encodeWithCoder(aCoder) aCoder.encodeFloat(Float(progress), forKey: "progress") aCoder.encodeObject(progressDelegate as! AnyObject?, forKey: "progressDelegate") } init(progressDelegate: TAProgressLayerProtocol?) { super.init() self.progressDelegate = progressDelegate } // MARK: - Progress Reporting // Override needsDisplayForKey so that we can define progress as being animatable. class override func needsDisplayForKey(key: String) -> Bool { if (key == "progress") { return true } else { return super.needsDisplayForKey(key) } } // Call our callback override func drawInContext(ctx: CGContext) { if let del = self.progressDelegate { del.progressUpdated(progress) } } }