为啥当我为我的 CABasicAnimation 设置低持续时间值时它会跳跃?

Posted

技术标签:

【中文标题】为啥当我为我的 CABasicAnimation 设置低持续时间值时它会跳跃?【英文标题】:Why when I set a low duration value for my CABasicAnimation does it jump?为什么当我为我的 CABasicAnimation 设置低持续时间值时它会跳跃? 【发布时间】:2014-03-20 01:29:29 【问题描述】:

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

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

动画设置如下:

- (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;

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

- (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,动画中会出现跳跃。它顺时针向前一点,然后向后跳,然后像往常一样继续向前。 如果将持续时间或进度设置得更高,则不会发生这种情况,这毫无价值。

如何停止跳跃?此代码在所有情况下都运行良好,除非进度非常低。我做错了什么?

【问题讨论】:

不要在begincommit之外配置事务。你最终可能会影响其他事情。 【参考方案1】:

啊!我们应该早点看到这一点。问题是if (animated) 块中的这一行:

self.innerPie.strokeEnd = progress;

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

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

(注意setDisableActions: 在开始/提交中的位置。)

或者,您可以删除自己的动画并只使用自动动画,使用 setAnimationDuration: 之类的东西来更改其长度。

原建议:

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

如果这不起作用,我建议在每次调用该方法时添加日志语句来跟踪progress[self.innerPie.presentationLayer strokeEnd] 的值;也许他们没有做你期望的事情。

【讨论】:

嗯,我将它移出drawRect,并在调用setProgress:animated: 时开始记录,但都没有帮助。日志记录仅在第一次调用 strokeEnd 和 progress 时显示 0.0 以将其设置为 0.1,这是唯一一次调用它。这是一个示例项目:cl.ly/1W3V3b0D2001 @DougSmith 根据您的示例项目,我已经更新了我的答案。 这是证明解决方案的documentation 的链接。【参考方案2】:

你为什么使用drawRect:?你的类中应该没有drawRect: 方法。

如果你也在使用 self.layer,你不应该使用 drawRect。使用其中一种,不要同时使用。

将其更改为:

- (id)initWithFrame:(CGRect)frame

  if (!(self = [super initWithFrame:frame])
    return nil;

  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;

  return self;

【讨论】:

我将它移出drawRect,但没有任何改变。这是一个示例项目:cl.ly/1W3V3b0D2001 将其移至 initWithFrame 是正确的做法,但并没有解决问题。抱歉,我以前从未与CAShapeLayer 合作过,但是我查看了您的代码并在进行此更改后:dl.dropboxusercontent.com/u/147461/… 它完美运行(动画:是)。我不确定你如何让它与动画一起工作:NO,并且没有明显的方法来控制动画速度。我必须进一步研究 CAShapeLayer。 基本上看起来 CAShapeLayer 为你做了所有的动画工作,如果你尝试在上面添加自己的动画,它就会失败。大概有一些方法可以正确地制作动画,我不确定它是什么。【参考方案3】:

在相同距离内行驶时,持续时间越短,速度越高。当你将持续时间设置得太小时,速度会非常高,这就是为什么它看起来像跳跃的原因。

【讨论】:

这不是正在发生的事情。正如我上面所描述的,它会向前跳跃,这将是完美的,然后它会跳回到原来的起点然后重新开始。这不是高速的结果。

以上是关于为啥当我为我的 CABasicAnimation 设置低持续时间值时它会跳跃?的主要内容,如果未能解决你的问题,请参考以下文章

为啥当我为 C++ 代码打印字符时,我的终端会发出哔声?

当我将条目插入现有部分时,为啥我的 UITableView 会为我添加另一个部分?

为啥当我单击一个链接时,它会为我的所有链接执行该功能? (Javascipt)

为啥当我没有为我的 openGLWidget 创建额外的类时 glRotate 只工作一次?

我为类编写了这个汉明编码代码。为啥这么慢?

为啥实时 AppEngine 没有调用我的 servlet 过滤器?