为什么当我为CABasicAnimation设置较低的持续时间值时,它会跳吗?

为什么当我为CABasicAnimation设置较低的持续时间值时,它会跳吗?

问题描述:

示例项目 http://cl.ly/1W3V3b0D2001

我正在使用 CABasicAnimation 创建一个类似于饼形图的进度指示器。类似于iOS 7应用下载动画:

I'm using CABasicAnimation to create a progress indicator that is like a pie chart. Similar to the iOS 7 app download animation:

动画设置如下:

- (void)drawRect:(CGRect)rect
{
    [super drawRect:rect];

    CGFloat radius = CGRectGetWidth(self.frame) / 2;
    CGFloat inset  = 1;
    CAShapeLayer *ring = [CAShapeLayer layer];
    ring.path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(self.bounds, inset, inset)
                                           cornerRadius:radius-inset].CGPath;

    ring.fillColor = [UIColor clearColor].CGColor;
    ring.strokeColor = [UIColor whiteColor].CGColor;
    ring.lineWidth = 2;

    self.innerPie = [CAShapeLayer layer];
    inset = radius/2;
    self.innerPie.path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(self.bounds, inset, inset)
                                               cornerRadius:radius-inset].CGPath;
    self.innerPie.fillColor = [UIColor clearColor].CGColor;
    self.innerPie.strokeColor = [UIColor whiteColor].CGColor;
    self.innerPie.lineWidth = (radius-inset)*2;

    self.innerPie.strokeStart = 0;
    self.innerPie.strokeEnd = 0;

    [self.layer addSublayer:ring];
    [self.layer addSublayer:self.innerPie];

    self.progress = 0.0;
}

通过设置视图进度来触发动画:

The animation is triggered by setting the progress of the view:

- (void)setProgress:(CGFloat)progress animated:(BOOL)animated {
    self.progress = progress;

    if (animated) {
        CGFloat totalDurationForFullCircleAnimation = 0.25;

        CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
        self.innerPie.strokeEnd = progress;
        pathAnimation.delegate = self;
        pathAnimation.fromValue = @([self.innerPie.presentationLayer strokeEnd]);
        pathAnimation.toValue = @(progress);
        pathAnimation.duration = totalDurationForFullCircleAnimation * ([pathAnimation.toValue floatValue] - [pathAnimation.fromValue floatValue]);

        [self.innerPie addAnimation:pathAnimation forKey:@"strokeEndAnimation"];
    }
    else {
        [CATransaction setDisableActions:YES];
        [CATransaction begin];
        self.innerPie.strokeEnd = progress;
        [CATransaction commit];
    }
}

但是,如果我将进度设置为某些值较小,例如 0.25 ,动画中会有跳跃。它沿顺时针方向前进一点,然后跳回原处,然后继续正常前进。

However, in cases where I set the progress to something small, such as 0.25, there's a jump in the animation. It goes a little forward clockwise, jumps back, then keeps going forward as normal. It's worth nothing that this does not happen if the duration or progress is set higher.

我如何停止跳跃?该代码在任何情况下都可以正常工作,除非进度很慢。我在做什么错?

How do I stop the jump? This code works well in every case except when the progress is very low. What am I doing wrong?

啊!我们应该早就看到了。问题是您的 if(动画)块中的这一行:

Ah! We should have seen this earlier. The problem is this line in your if (animated) block:

self.innerPie.strokeEnd = progress;

由于innerPie是Core Animation层,因此会导致隐式动画(大多数属性更改都会这样做)。该动画正在与您自己的动画战斗。您可以通过在设置strokeEnd时禁用隐式动画来防止这种情况的发生:

Since innerPie is a Core Animation layer, this causes an implicit animation (most property changes do). This animation is fighting with your own animation. You can prevent this from happening by disabling implicit animation while setting the strokeEnd:

[CATransaction begin];
[CATransaction setDisableActions:YES];
self.innerPie.strokeEnd = progress;
[CATransaction commit];

(请注意 setDisableActions:在开始/提交。)

(Note how setDisableActions: is within the begin/commit.)

或者,您可以删除自己的动画并使用自动动画,例如 setAnimationDuration:更改其长度。

Alternatively, you can remove your own animation and just use the automatic one, using something like setAnimationDuration: to change its length.

原始建议:

我的猜测是您的 drawRect:被调用,这将重置您的strokeEnd值。无论如何,这些层可能不应该在 drawRect:中设置。尝试将该设置移至init方法或 didMoveToWindow:或类似方法。

My guess is that your drawRect: is being called, which resets your strokeEnd value. Those layers should probably not be set up in drawRect: anyway. Try moving that setup to an init method or didMoveToWindow: or similar.

如果这样无效,我建议添加日志语句以跟踪 progress 和 [self.innerPie.presentationLayer strokeEnd] 每次调用该方法;也许他们没有按照您的期望去做。

If that's not effective, I would suggest adding log statements to track the value of progress and [self.innerPie.presentationLayer strokeEnd] each time the method is called; perhaps they're not doing what you expect.