【问题标题】:CAShapeLayer animation flash when I set duration very small当我将持续时间设置得非常小时时,CAShapeLayer 动画会闪烁
【发布时间】:2016-04-12 10:30:05
【问题描述】:

我想实现一个回合的进度,进度值可以动态设置。代码如下:

- (CGPoint)center {
  return CGPointMake(self.view.frame.origin.x + self.view.frame.size.width / 2.0,
                 self.view.frame.origin.y + self.view.frame.size.height / 2.0);
}

- (void)viewDidLoad {
  [super viewDidLoad];

  // Do any additional setup after loading the view.

  CGMutablePathRef roundPath = CGPathCreateMutable();
  CGPathAddArc(roundPath, NULL, self.center.x, self.center.y,
           20,
           2 * M_PI + M_PI_2,
           M_PI_2,
           YES);

  CAShapeLayer *backgroundLayer = [CAShapeLayer layer];
  backgroundLayer.frame = self.view.layer.bounds;
  backgroundLayer.path = roundPath;
  backgroundLayer.strokeColor = [[NSColor blueColor] CGColor];
  backgroundLayer.fillColor = nil;
  backgroundLayer.lineWidth = 10.0f;
  backgroundLayer.lineJoin = kCALineJoinBevel;
  [self.view.layer addSublayer:backgroundLayer];

  CAShapeLayer *pathLayer = [CAShapeLayer layer];
  pathLayer.frame = self.view.layer.bounds;
  pathLayer.path = roundPath;
  pathLayer.strokeColor = [[NSColor whiteColor] CGColor];
  pathLayer.fillColor = nil;
  pathLayer.lineWidth = 10.0f;
  pathLayer.lineJoin = kCALineJoinBevel;
  [self.view.layer addSublayer:pathLayer];

  self.pathLayer = pathLayer;
  [self start];
}

- (void)start {
  CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
  pathAnimation.duration = 0.01;
  pathAnimation.fromValue = [NSNumber numberWithFloat:self.progress];
  pathAnimation.toValue = [NSNumber numberWithFloat:self.progress+0.01];
  [self.pathLayer setStrokeEnd:self.progress + 0.01];
  [pathAnimation setDelegate:self];
  [self.pathLayer addAnimation:pathAnimation forKey:@"strokeEndAnimation"];
}

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
  self.progress += 0.01;
  if (self.progress < 1.0) {
  [self start];
  }
}

我发现当我将持续时间设置为 0.1f 甚至更大时,它会正常工作。但是如果我将持续时间设置为 0.01f,动画将不会从正确的值开始,它会从更大的值开始动画减少到正确的值。所以整个动画总是闪烁,有人有同样的问题或知道为什么吗?非常感谢!

【问题讨论】:

    标签: objective-c macos animation


    【解决方案1】:

    originaluser2 关于具体原因可能是对的,但这种设计是错误的。

    在 60fps 时,一帧是 0.0167 秒。您要求在不到一帧的时间内为更改设置动画。这些动画中的每一个都有自己的媒体时间 (kCAMediaTimingFunctionDefault),这意味着您可以通过动画创建复杂的上升/下降速度。无论如何,您的时间安排都会不稳定,因为您在每个步骤中都会遇到一些错误(animationDidStop 可能需要稍微不同的时间来运行,具体取决于许多因素)。动画的全部意义在于你不需要做这种事情。这就是为什么你有一个动画引擎。

    只需在您希望的时间内为progress 设置动画即可。不要尝试注入许多额外的动画步骤。注入步骤是动画引擎所做的。

    CALayer 旨在为您完成大部分此类工作。您不需要为此创建明确的动画;只使用隐式。类似的东西(未经测试,未编译):

    - (void)setProgress: (CGFloat)progress {
        double velocity = 1.0;
        [CATransaction begin];
        double delta = fabs(_progress - progress);
        [CATransaction setAnimationDuration: delta * velocity];
        [self.pathLayer setStrokeEnd: progress];
        [CATransaction commit];
    }
    

    【讨论】:

    • 一个很好的观点 (+1)...不敢相信我错过了这么明显的东西!我想我认为 OP 使用重复动画有一个绝妙的理由......我唯一能想到的就是能够暂停动画 - 但如果需要,OP 可以只使用单个显式动画。
    • 非常感谢您的帮助,为我节省了很多时间。
    • 感谢您的帮助,我只知道我应该单击检查以标记已接受的答案。
    【解决方案2】:

    作为Rob says,添加重复动画并不是最好的主意。他的解决方案可能对您很有效。但是,如果您仍然坚持使用重复的动画,这里是您当前代码的修复:


    问题是您在添加动画之前调用了这行代码。

    [self.pathLayer setStrokeEnd:self.progress + 0.01];
    

    这将在图层上创建一个隐式动画,因此当您添加显式动画时 – 它会导致问题(您正在观察的闪烁)。

    解决方案是在您启动动画后在CATransaction 内更新模型层。您还需要将disableActions 设置为YES 以防止生成隐式动画。

    例如:

    - (void)start {
        CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
        pathAnimation.duration = 0.01;
        pathAnimation.fromValue = [NSNumber numberWithFloat:self.progress];
        pathAnimation.toValue = [NSNumber numberWithFloat:self.progress+0.01];
        [pathAnimation setDelegate:self];
        [self.pathLayer addAnimation:pathAnimation forKey:@"strokeEndAnimation"];
    
        [CATransaction begin];
        [CATransaction setDisableActions:YES];
        [self.pathLayer setStrokeEnd:self.progress + 0.01];
        [CATransaction commit];
    }
    

    不过,同样值得注意的是,您可以只使用 CATransactionstrokeEnd 的隐式动画来创建动画。

    例如:

    - (void)start {
    
        [CATransaction begin];
        [CATransaction setAnimationDuration:0.01];
        [CATransaction setCompletionBlock:^{
            self.progress += 0.01;
    
            if (self.progress < 1.0) {
                [self start];
            }
        }];
        [self.pathLayer setStrokeEnd:self.progress + 0.01];
        [CATransaction commit];
    }
    

    这样您就不必在每次迭代时都创建新的显式动画。

    【讨论】:

    • 多谢帮助,终于知道原因了~
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-15
    • 1970-01-01
    • 2012-10-07
    相关资源
    最近更新 更多