核心动画进度回调

tar*_*mes 19 core-animation ios

当Core Animation在运行时到达某些点时(例如,完成时的50%和66%),是否有一种简单的方法可以回叫?

我正在考虑建立一个NSTimer,但这并不像我想的那么准确.

tar*_*mes 33

我终于为这个问题开发了一个解决方案.

基本上我希望每一帧都被召回,并做我需要做的事情.

没有明显的方法来观察动画的进度,但它实际上是可能的:

  • 首先,我们需要创建一个新的CALayer子类,它具有一个名为"progress"的可动画属性.

  • 我们将图层添加到树中,然后创建一个动画,在动画持续时间内将进度值从0驱动到1.

  • 由于我们的progress属性可以设置动画,因此我们的子类会为动画的每个帧调用drawInContext.这个函数不需要重绘任何东西,但它可以用来调用委托函数:)

这是类接口:

@protocol TAProgressLayerProtocol <NSObject>

- (void)progressUpdatedTo:(CGFloat)progress;

@end

@interface TAProgressLayer : CALayer

@property CGFloat progress;
@property (weak) id<TAProgressLayerProtocol> delegate;

@end
Run Code Online (Sandbox Code Playgroud)

并实施:

@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
Run Code Online (Sandbox Code Playgroud)

然后我们可以将图层添加到主图层:

TAProgressLayer *progressLayer = [TAProgressLayer layer];
progressLayer.frame = CGRectMake(0, -1, 1, 1);
progressLayer.delegate = self;
[_sceneView.layer addSublayer:progressLayer];
Run Code Online (Sandbox Code Playgroud)

并将其与其他动画一起制作动画:

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"];
Run Code Online (Sandbox Code Playgroud)

最后,随着动画的进行,代理将被回调:

- (void)progressUpdatedTo:(CGFloat)progress
{
    // Do whatever you need to do...
}
Run Code Online (Sandbox Code Playgroud)

  • 真棒的想法.我想建议一个小改进.您当前被迫将图层的帧设置为1x1 rect,因为除非图层的帧非零(在此过程中创建1x1pt位图),否则不会调用`drawInContext:`.相反,您可以覆盖`[CALayer display]`,_will_将被调用为零帧,并且默认情况下不会创建位图上下文(稍微提高效率).唯一的区别是你需要在`draw`实现中读取`self.presentationLayer.progress`而不是`self.progress`. (6认同)

smi*_*org 12

如果您不想破解CALayer向您报告进度,那么还有另一种方法.从概念上讲,您可以使用CADisplayLink来保证每个帧的回调,然后简单地测量自动画开始以来经过的时间除以确定完成百分比的持续时间.

开源库INTUAnimationEngine将这个功能非常干净地打包成一个看起来几乎与UIView基于块的动画一样的API:

// INTUAnimationEngine.h

// ...

+ (NSInteger)animateWithDuration:(NSTimeInterval)duration
                           delay:(NSTimeInterval)delay
                      animations:(void (^)(CGFloat percentage))animations
                      completion:(void (^)(BOOL finished))completion;

// ...
Run Code Online (Sandbox Code Playgroud)

您需要做的就是在启动其他动画的同时调用此方法,为duration和传递相同的值delay,然后对于动画的每个帧,animations将以当前完成百分比执行块.如果您想让您的时间完全同步,那么您可以完全从INTUAnimationEngine开始制作动画.


Jef*_*Hay 6

我在接受的答案中做了一个由tarme建议的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)
        }
    }

}
Run Code Online (Sandbox Code Playgroud)