动画期间无法获取 CALayer 的当前位置

Posted

技术标签:

【中文标题】动画期间无法获取 CALayer 的当前位置【英文标题】:Cannot get current position of CALayer during animation 【发布时间】:2012-12-15 03:06:18 【问题描述】:

我正在尝试实现一个动画,当你按住一个按钮时,它会动画一个块,当你释放时,它会动画回到原来的位置,但是我无法获得动画块的当前位置没有有什么关系。这是我的代码:

-(IBAction)moveDown:(id)sender

   CGRect position = [[container.layer presentationLayer] frame];

    [movePath moveToPoint:CGPointMake(container.frame.origin.x, position.y)];

    [movePath addLineToPoint:CGPointMake(container.frame.origin.x, 310)];

    CAKeyframeAnimation *moveAnim = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    moveAnim.path = movePath.CGPath;
    moveAnim.removedOnCompletion = NO;
    moveAnim.fillMode = kCAFillModeForwards;


    CAAnimationGroup *animGroup = [CAAnimationGroup animation];
    animGroup.animations = [NSArray arrayWithObjects:moveAnim, nil];
    animGroup.duration = 2.0;
    animGroup.removedOnCompletion = NO;
    animGroup.fillMode = kCAFillModeForwards;

    [container.layer addAnimation:animGroup forKey:nil];

-(IBAction)moveUp:(id)sender
     CGRect position = [[container.layer presentationLayer] frame];

    UIBezierPath *movePath = [UIBezierPath bezierPath];

    [movePath moveToPoint:CGPointMake(container.frame.origin.x, position.y)];

    [movePath addLineToPoint:CGPointMake(container.frame.origin.x, 115)];

    CAKeyframeAnimation *moveAnim = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    moveAnim.path = movePath.CGPath;
    moveAnim.removedOnCompletion = NO;
    moveAnim.fillMode = kCAFillModeForwards;

    CAAnimationGroup *animGroup = [CAAnimationGroup animation];
    animGroup.animations = [NSArray arrayWithObjects:moveAnim, nil];
    animGroup.duration = 2.0;
    animGroup.removedOnCompletion = NO;
    animGroup.fillMode = kCAFillModeForwards;

    [container.layer addAnimation:animGroup forKey:nil];


但是行

   CGRect position = [[container.layer presentationLayer] frame];

只返回目标位置而不是当前位置。一旦我释放按钮,我基本上需要给我动画容器的当前位置,这样我就可以执行下一个动画。我现在拥有的东西不起作用。

【问题讨论】:

【参考方案1】:

我还没有对您的代码进行足够的分析,无法 100% 确定为什么 [[container.layer presentationLayer] frame] 可能不会返回您期望的结果。但我发现了几个问题。

一个明显的问题是moveDown: 没有声明movePath。如果movePath 是一个实例变量,您可能希望在每次调用moveDown: 时清除它或创建一个新实例,但我看不到您这样做。

一个不太明显的问题是(从你对removedOnCompletionfillMode 的使用来看,尽管你使用了presentationLayer)你显然不明白Core Animation 是如何工作的。事实证明这很常见,如果我错了,请原谅我。无论如何,请继续阅读,因为我将解释 Core Animation 的工作原理以及如何解决您的问题。

在Core Animation 中,您通常使用的图层对象是模型图层。当您将动画附加到层时,Core Animation 会创建模型层的副本,称为 表示层,并且动画会随着时间的推移更改表示层的属性。动画永远不会改变模型层的属性。

当动画结束并且(默认情况下)被移除时,表示层被销毁,模型层的属性值再次生效。因此,屏幕上的图层似乎“弹回”到其原始位置/颜色/其他。

解决此问题的一种常见但错误的方法是将动画的removedOnCompletion 设置为NO,并将其fillMode 设置为kCAFillModeForwards。执行此操作时,表示层会挂起,因此屏幕上没有“回弹”。问题是现在您的表示层与模型层具有不同的值。如果您向模型层(或拥有它的视图)询问动画属性的值,您将得到一个与屏幕上不同的值。如果您再次尝试为该属性设置动画,则动画可能会从错误的位置开始。

要为图层属性设置动画并使其“粘贴”,您需要更改模型图层的属性值,然后应用动画。这样,当动画被移除,表示层消失时,屏幕上的层看起来完全一样,因为模型层的属性值与动画结束时它的表示层的属性值相同。

现在,我不知道您为什么要使用关键帧来制作直线运动的动画,或者为什么要使用动画组。这里似乎都没有必要。而且你的两种方法实际上是相同的,所以让我们把通用代码分解出来:

- (void)animateLayer:(CALayer *)layer toY:(CGFloat)y 
    CGPoint fromValue = [layer.presentationLayer position];
    CGPoint toValue = CGPointMake(fromValue.x, y);

    layer.position = toValue;

    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
    animation.fromValue = [NSValue valueWithCGPoint:fromValue];
    animation.toValue = [NSValue valueWithCGPoint:toValue];
    animation.duration = 2;
    [layer addAnimation:animation forKey:animation.keyPath];

请注意,当我将动画添加到图层时,我们会为其赋予一个键。由于我们每次都使用相同的键,如果之前的动画还没有完成,每个新动画都会替换(删除)之前的动画。

当然,当你玩这个的时候,你会发现如果你moveUp:moveDown:只完成了一半,moveUp:动画就会出现一半的速度,因为它还有持续时间为 2 秒,但只有行进距离的一半。我们真的应该根据要行驶的距离来计算持续时间:

- (void)animateLayer:(CALayer *)layer toY:(CGFloat)y withBaseY:(CGFloat)baseY 
    CGPoint fromValue = [layer.presentationLayer position];
    CGPoint toValue = CGPointMake(fromValue.x, y);

    layer.position = toValue;

    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
    animation.fromValue = [NSValue valueWithCGPoint:fromValue];
    animation.toValue = [NSValue valueWithCGPoint:toValue];
    animation.duration = 2.0 * (toValue.y - fromValue.y) / (y - baseY);
    [layer addAnimation:animation forKey:animation.keyPath];

如果您真的需要它作为动画组中的关键路径动画,您的问题应该告诉我们为什么您需要这些东西。无论如何,它也适用于这些东西:

- (void)animateLayer:(CALayer *)layer toY:(CGFloat)y withBaseY:(CGFloat)baseY 
    CGPoint fromValue = [layer.presentationLayer position];
    CGPoint toValue = CGPointMake(fromValue.x, y);

    layer.position = toValue;

    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:fromValue];
    [path addLineToPoint:toValue];

    CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    animation.path = path.CGPath;
    animation.duration =  2.0 * (toValue.y - fromValue.y) / (y - baseY);

    CAAnimationGroup *group = [CAAnimationGroup animation];
    group.duration = animation.duration;
    group.animations = @[animation];
    [layer addAnimation:group forKey:animation.keyPath];

你可以找到我的测试程序in this gist的完整代码。只需创建一个新的 Single View Application 项目并将 ViewController.m 的内容替换为 gist 的内容即可。

【讨论】:

非常感谢。我是使用动画的新手,这对我有很大帮助!我会试一试! 观看来自 WWDC 2011 的 Session 421 - Core Animation Essentials 视频。只有一个小时,它非常信息丰富。 我正在使用 kCAFillModeForwards,而这件事让我很痛苦。感谢您的详尽解释。 那么 ios 8 呢?表示层不可靠

以上是关于动画期间无法获取 CALayer 的当前位置的主要内容,如果未能解决你的问题,请参考以下文章

在动画期间无法正确定位子图层

为啥动画自定义 CALayer 属性会导致其他属性在动画期间为零?

动画期间未调用 CALayer setPosition

如何在更改其边界的动画期间强制重绘 CALayer

在动画期间访问 UIView 的当前位置

图像更改 CALayer 核心动画