在iOS中更改CAKeyframeAnimation中间动画的速度

时间:2013-05-31 18:49:02

标签: ios objective-c animation core-animation cakeyframeanimation

我试图让用户改变动画的速度。我正在使用bezier路径制作CAKeyframeAnimation,我可以让它显示并正确运行。 我尝试通过创建具有不同持续时间的新动画路径来更改速度。飞机回到起点(我还没有尝试修理)并加快速度。它们被绘制的路径在动画永远不会改变速度时消失。当飞机完成时,另一个出现在动画首先暂停的位置。我不知道我做错了什么。我的问题类似于这个modifying dynamically the duration of CAKeyframeAnimation,但我不明白OP对最终使用块的说法。

//The first two methods are in a class subclassing UIView
/** Pause each plane's animation */
- (void)pauseAnimation
{    
    CFTimeInterval pausedTime = [[self layer] convertTime:CACurrentMediaTime() fromLayer:nil];
    [self layer].speed = 0.0;
    [self layer].timeOffset = pausedTime;
}

/** Resume each plane's animation */
- (void)resumeAnimation
{
    CFTimeInterval pausedTime = [[self layer] timeOffset];
    [self layer].speed = 1.0;
    [self layer].timeOffset = 0.0;
    CFTimeInterval timeSincePause = [[self layer] convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;

    for(SEPlane *plane in planes){
        plane.planeAnimationPath.speedMultiplier = 5;
        [plane.planeAnimationPath beginAnimation:self];
    }
    //[self layer].beginTime = timeSincePause;
}

//This method is in the class of planeAnimationPath
/** Begin animating plane along given path */
- (void)beginAnimation:(UIView *) view
{
    planeAnimation = nil;
    // Create animation layer for animating plane
    planeAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];    
    planeAnimation.path = [bezierPath CGPath];
    planeAnimation.duration = approximateLength/(ANIMATION_SPEED * self.speedMultiplier);
    planeAnimation.calculationMode = kCAAnimationPaced;
    planeAnimation.fillMode = kCAFillModeForwards;
    planeAnimation.rotationMode = kCAAnimationRotateAuto;
    planeAnimation.removedOnCompletion = YES;
    [planeAnimation setDelegate:self];    

    // Add animation to image-layer
    [imageLayer addAnimation:planeAnimation forKey:animationKey];

    // Add image-layer to view
    [[view layer] addSublayer:imageLayer];
}

1 个答案:

答案 0 :(得分:2)

与从当前位置动画到目标位置的默认动画不同,CAKeyframeAnimations不会(据我所知)。除了你如何解释当前位置不在路径上的动画?

我能想到的最简单的选择是在speedMultiplier的setter中执行以下操作:

  1. 使用所需路径创建新动画。
  2. 将持续时间设置为speedMultiplier为1
  3. 将速度设置为speedMultiplier
  4. 将timeOffset设置为持续时间*“已完成新动画的百分比”
  5. 将动画添加到图层。
  6. 正如您可能已经猜到的,棘手的部分是第4步。对于简单的路径,这很容易,但对于任意路径,它会变得更复杂一些。作为起点,您需要贝塞尔二次曲线和三次曲线的公式。搜索“贝塞尔曲线的距离参数化”,你会发现很多东西。

    以下是简单矩形路径的代码示例。该窗口只有一个MPView和一个滑块:

    @implementation MPView {
    
        IBOutlet NSSlider *_slider;  // Min=0.0, Max=5.0
    
        CALayer  *_hostLayer;
        CALayer  *_ballLayer;
    
        CAKeyframeAnimation *_ballPositionAnimation;
    
        double _speed;
    }
    
    - (void) awakeFromNib
    {
        CGRect bounds = self.bounds;
    
        [CATransaction begin];
        [CATransaction setDisableActions:YES];
    
        _speed = 1.0;
    
        _hostLayer = [CALayer layer];
        _hostLayer.backgroundColor = CGColorGetConstantColor(kCGColorBlack);
        self.layer = _hostLayer;
        self.wantsLayer = YES;
    
        _ballLayer = [CALayer layer];
        _ballLayer.bounds = CGRectMake(0, 0, 32, 32);
        _ballLayer.position = CGPointMake(40, 40);
        _ballLayer.backgroundColor = CGColorGetConstantColor(kCGColorWhite);
        _ballLayer.cornerRadius = 16;
    
        _hostLayer.sublayers = @[_ballLayer];
    
    
        CGMutablePathRef path = CGPathCreateMutable();
        CGPathMoveToPoint(path, NULL, _ballLayer.position.x, _ballLayer.position.y);
        CGPathAddRect(path, NULL, CGRectInset(bounds, 40, 40));
        CGPathCloseSubpath(path);
    
        _ballPositionAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
        _ballPositionAnimation.path = path;
        _ballPositionAnimation.duration = 6;
        _ballPositionAnimation.repeatCount = HUGE_VALF;
    
        CGPathRelease(path);
    
        [_ballLayer addAnimation:_ballPositionAnimation forKey:_ballPositionAnimation.keyPath];
    
        [CATransaction commit];
    
        [_slider bind:NSValueBinding toObject:self withKeyPath:@"speed" options:@{NSContinuouslyUpdatesValueBindingOption:@YES}];
    }
    
    - (double) speed
    {
        return _speed;
    }
    
    - (void) setSpeed:(double)speed
    {
        _speed = speed;
    
        CGPoint pos = [(CALayer*)_ballLayer.presentationLayer position];
    
        [CATransaction begin];
        [CATransaction setDisableActions:YES];
    
        _ballPositionAnimation.speed = _speed;
        _ballPositionAnimation.duration = 5.0;
        _ballPositionAnimation.timeOffset = _ballPositionAnimation.duration * [self percentOfPathCompleted:pos];
        [_ballLayer addAnimation:_ballPositionAnimation forKey:_ballPositionAnimation.keyPath];
    
        [CATransaction commit];
    }
    
    - (double) percentOfPathCompleted:(CGPoint)p
    {
        CGRect rect = CGRectInset(self.bounds, 40, 40);
        double minX = NSMinX(rect);
        double minY = NSMinY(rect);
        double maxX = NSMaxX(rect);
        double maxY = NSMaxY(rect);
        double offset = 0.0;
    
        if (p.x == minX && p.y == minY)
            return 0.0;
        else if (p.x > minX && p.y == minY)
            offset = (p.x - minX) / rect.size.width * 0.25;
        else if (p.x == maxX && p.y < maxY)
            offset = (p.y - minY) / rect.size.height * 0.25 + 0.25;
        else if (p.x > minX && p.y == maxY)
            offset = (1.0 - (p.x - minX) / rect.size.width) * 0.25 + 0.50;
        else
            offset = (1.0 - (p.y - minY) / rect.size.height) * 0.25 + 0.75;
    
        NSLog(@"Offset = %f",offset);
        return offset;
    }
    
    @end