CABasicAnimation 在动画完成后重置为初始值

我正在旋转一个 CALayer,并试图在动画完成后在它的最终位置停止它。

但动画完成后,它重置到其初始位置。

(xcode 文档明确表示动画不会更新属性的值。)

如何实现这一目标的任何建议。

59130 次浏览

设置以下属性:

animationObject.removedOnCompletion = NO;

这就是答案,是我的答案和克里希南的答案的结合。

cabasicanimation.fillMode = kCAFillModeForwards;
cabasicanimation.removedOnCompletion = NO;

默认值是 kCAFillModeRemoved(这是您正在看到的重置行为。)

把它放进你的代码里

CAAnimationGroup *theGroup = [CAAnimationGroup animation];


theGroup.fillMode = kCAFillModeForwards;


theGroup.removedOnCompletion = NO;

RemovedOnCompletion 的问题在于 UI 元素不允许用户交互。

我的技术是设置动画中的 FROM 值和对象上的 TO 值。 动画将在开始之前自动填充 TO 值,当它被移除时将使对象处于正确的状态。

// fade in
CABasicAnimation *alphaAnimation = [CABasicAnimation animationWithKeyPath: @"opacity"];
alphaAnimation.fillMode = kCAFillModeForwards;


alphaAnimation.fromValue = NUM_FLOAT(0);
self.view.layer.opacity = 1;


[self.view.layer addAnimation: alphaAnimation forKey: @"fade"];

您可以简单地将 CABasicAnimation的键设置为 position,当您将它添加到层时。通过这样做,它将覆盖对运行循环中当前传递的位置所做的隐式动画。

CGFloat yOffset = 30;
CGPoint endPosition = CGPointMake(someLayer.position.x,someLayer.position.y + yOffset);


someLayer.position = endPosition; // Implicit animation for position


CABasicAnimation * animation =[CABasicAnimation animationWithKeyPath:@"position.y"];


animation.fromValue = @(someLayer.position.y);
animation.toValue = @(someLayer.position.y + yOffset);


[someLayer addAnimation:animation forKey:@"position"]; // The explicit animation 'animation' override implicit animation

您可以有更多的信息在2011年苹果 WWDC 视频会议421-核心动画要点(视频中间)

@ Leslie Godwin 的回答不是很好,“ self. view.layer.opacity = 1;”马上就完成了(大约需要一秒钟) ,如果你有疑问,请将 alphaAnimation.duration 修改为10.0。 你得把这条线移开。

因此,当您将 filMode 修复为 kCAFillModeForward 并将 OnCompletion 移除为 NO 时,可以让动画保留在图层中。如果您修复了动画委托,并尝试这样的东西:

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
[theLayer removeAllAnimations];
}

在你执行这一行的时候,图层会立即恢复,这是我们想要避免的。

您必须修复图层属性,然后才能从中删除动画。请尝试这样做:

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
if([anim isKindOfClass:[CABasicAnimation class] ]) // check, because of the cast
{
CALayer *theLayer = 0;
if(anim==[_b1 animationForKey:@"opacity"])
theLayer = _b1; // I have two layers
else
if(anim==[_b2 animationForKey:@"opacity"])
theLayer = _b2;


if(theLayer)
{
CGFloat toValue = [((CABasicAnimation*)anim).toValue floatValue];
[theLayer setOpacity:toValue];


[theLayer removeAllAnimations];
}
}
}

CALayer 有一个模型层和一个表示层。在动画期间,表示层独立于模型进行更新。当动画完成时,表示层将用来自模型的值进行更新。如果您想避免动画结束后的震动跳转,关键是保持两个层的同步。

如果知道最终值,可以直接设置模型。

self.view.layer.opacity = 1;

但是,如果你有一个动画,你不知道结束位置(例如,一个缓慢的淡出,用户可以暂停,然后反向) ,然后你可以查询表示层直接找到当前值,然后更新模型。

NSNumber *opacity = [self.layer.presentationLayer valueForKeyPath:@"opacity"];
[self.layer setValue:opacity forKeyPath:@"opacity"];

从表示层提取值对于缩放或旋转键路径特别有用(例如 transform.scaletransform.rotation)

这种方法是有效的:

let animation = CABasicAnimation(keyPath: "opacity")
animation.fromValue = 0
animation.toValue = 1
animation.duration = 0.3


someLayer.opacity = 1 // important, this is the state you want visible after the animation finishes.
someLayer.addAnimation(animation, forKey: "myAnimation")

在动画过程中,核心动画在你的正常图层上面显示了一个“表示层”。因此,设置不透明度(或任何东西) ,当动画完成和表示层消失时,你希望看到什么。在 之前行上执行此操作,添加动画以避免完成时出现闪烁。

如果您希望延迟,请执行以下操作:

let animation = CABasicAnimation(keyPath: "opacity")
animation.fromValue = 0
animation.toValue = 1
animation.duration = 0.3
animation.beginTime = someLayer.convertTime(CACurrentMediaTime(), fromLayer: nil) + 1
animation.fillMode = kCAFillModeBackwards // So the opacity is 0 while the animation waits to start.


someLayer.opacity = 1 // <- important, this is the state you want visible after the animation finishes.
someLayer.addAnimation(animation, forKey: "myAnimation")

最后,如果您使用‘ RemovedOnCompletion = false’,它将泄漏 CAAnimations,直到该层最终被处理-避免。

简单地设置 fillModeremovedOnCompletion对我不起作用。我通过将下面属性的 所有设置为 CABasicAnimation 对象解决了这个问题:

CABasicAnimation* ba = [CABasicAnimation animationWithKeyPath:@"transform"];
ba.duration = 0.38f;
ba.fillMode = kCAFillModeForwards;
ba.removedOnCompletion = NO;
ba.autoreverses = NO;
ba.repeatCount = 0;
ba.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.85f, 0.85f, 1.0f)];
[myView.layer addAnimation:ba forKey:nil];

此代码将 myView转换为其大小的85% (第三维不变)。

似乎 RemovedOnCompletion 标志设置为 false,而 filMode 设置为 kCAFillModeForward 对我来说也不起作用。

当我在一个图层上应用了新的动画之后,一个动画对象重置为它的初始状态,然后从这个状态开始动画。 另外需要做的是在设置新动画之前,根据表示层的属性设置模型层的期望属性,如下所示:

someLayer.path = ((CAShapeLayer *)[someLayer presentationLayer]).path;
[someLayer addAnimation:someAnimation forKey:@"someAnimation"];

不使用 removedOnCompletion

你可以试试这个技巧:

self.animateOnX(item: shapeLayer)


func animateOnX(item:CAShapeLayer)
{
let endPostion = CGPoint(x: 200, y: 0)
let pathAnimation = CABasicAnimation(keyPath: "position")
//
pathAnimation.duration = 20
pathAnimation.fromValue = CGPoint(x: 0, y: 0)//comment this line and notice the difference
pathAnimation.toValue =  endPostion
pathAnimation.fillMode = kCAFillModeBoth


item.position = endPostion//prevent the CABasicAnimation from resetting item's position when the animation finishes


item.add(pathAnimation, forKey: nil)
}

所以我的问题是,我试图旋转一个对象的平移姿态,所以我有多个相同的动画对每一个动作。我同时拥有 fillMode = kCAFillModeForwardsisRemovedOnCompletion = false,但它没有帮助。就我而言,我必须确保动画键是不同的,每次我添加一个新的动画:

let angle = // here is my computed angle
let rotate = CABasicAnimation(keyPath: "transform.rotation.z")
rotate.toValue = angle
rotate.duration = 0.1
rotate.isRemovedOnCompletion = false
rotate.fillMode = CAMediaTimingFillMode.forwards


head.layer.add(rotate, forKey: "rotate\(angle)")

核心动画维护两个层次结构: 模型层表示层。当动画正在进行时,模型层实际上是完整的,并保持其初始值。默认情况下,动画一旦完成就会被删除。然后表示层回到模型层的值。

简单地将 removedOnCompletion设置为 NO意味着动画不会被删除并且浪费内存。此外,模型层和表示层将不再同步,这可能会导致潜在的错误。

因此,将模型层上的属性直接更新为最终值将是一个更好的解决方案。

self.view.layer.opacity = 1;
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
animation.fromValue = 0;
animation.toValue = 1;
[self.view.layer addAnimation:animation forKey:nil];

如果上面第一行代码导致了任何隐式动画,尝试关闭 If:

[CATransaction begin];
[CATransaction setDisableActions:YES];
self.view.layer.opacity = 1;
[CATransaction commit];


CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
animation.fromValue = 0;
animation.toValue = 1;
[self.view.layer addAnimation:animation forKey:nil];

最简单的解决方案是使用隐式动画。这将为您解决所有这些问题:

self.layer?.backgroundColor = NSColor.red.cgColor;

如果你想自定义,例如持续时间,你可以使用 NSAnimationContext:

    NSAnimationContext.beginGrouping();
NSAnimationContext.current.duration = 0.5;
self.layer?.backgroundColor = NSColor.red.cgColor;
NSAnimationContext.endGrouping();

注意: 这只在 macOS 上测试。

我最初没有看到任何动画时,这样做。问题是视图支持层的层执行 没有隐式动画。为了解决这个问题,请确保您自己添加了一个图层(在将视图设置为层支持之前)。

如何做到这一点的一个例子是:

override func awakeFromNib() {
self.layer = CALayer();
//self.wantsLayer = true;
}

使用 self.wantsLayer对我的测试没有任何影响,但是它可能有一些我不知道的副作用。

下面是一个游乐场的例子:

import PlaygroundSupport
import UIKit


let resultRotation = CGFloat.pi / 2
let view = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 200.0, height: 300.0))
view.backgroundColor = .red


//--------------------------------------------------------------------------------


let rotate = CABasicAnimation(keyPath: "transform.rotation.z") // 1
rotate.fromValue = CGFloat.pi / 3 // 2
rotate.toValue = resultRotation // 3
rotate.duration = 5.0 // 4
rotate.beginTime = CACurrentMediaTime() + 1.0 // 5
// rotate.isRemovedOnCompletion = false // 6
rotate.fillMode = .backwards // 7


view.layer.add(rotate, forKey: nil) // 8
view.layer.setAffineTransform(CGAffineTransform(rotationAngle: resultRotation)) // 9


//--------------------------------------------------------------------------------


PlaygroundPage.current.liveView = view
  1. 创建动画模型
  2. 设置开始位置 动画(可以跳过,它取决于您当前的层布局)
  3. 设置终端位置 动画
  4. 设置动画持续时间
  5. 暂停一下动画
  6. 不要设置 falseisRemovedOnCompletion-让核心动画干净后,动画完成
  7. 这里是 魔术的第一部分-你说核心动画放置你的动画到开始位置(你在步骤 # 2设置)之前,甚至动画已经开始-延长它向后的时间
  8. 复制准备好的动画对象,将其添加到图层中,并在延迟后启动动画(在步骤 # 5中设置)
  9. 第二部分 是设置正确的结束位置 分层-动画被删除后,你的图层将显示在正确的地方