Objective-C - CABasicAnimation 在动画后应用更改?

Posted

技术标签:

【中文标题】Objective-C - CABasicAnimation 在动画后应用更改?【英文标题】:Objective-C - CABasicAnimation applying changes after animation? 【发布时间】:2012-07-17 03:30:36 【问题描述】:

我正在使用CABasicAnimation 来移动和调整图像视图的大小。我希望将图像视图添加到超级视图、动画,然后从超级视图中删除。

为了实现这一点,我正在监听我的 CAAnimationGroup 的委托调用,并且一旦它被调用,我就会从超级视图中删除图像视图。

问题是有时图像在从超级视图中删除之前会在初始位置闪烁。避免这种行为的最佳方法是什么?

CAAnimationGroup *animGroup = [CAAnimationGroup animation];
    animGroup.animations = [NSArray arrayWithObjects:moveAnim, scaleAnim, opacityAnim, nil];
    animGroup.duration = .5;
    animGroup.delegate = self;
    [imageView.layer addAnimation:animGroup forKey:nil];

【问题讨论】:

【参考方案1】:

当您向图层添加动画时,该动画不会更改图层的属性。相反,系统会创建图层的副本。原始层称为模型层,复制层称为表示层。表示层的属性随着动画的进行而变化,但模型层的属性保持不变。

当您删除动画时,系统会破坏表示层,只留下模型层,然后模型层的属性控制该层的绘制方式。因此,如果模型层的属性与表示层属性的最终动画值不匹配,该层将立即重置为动画之前的外观。

要解决这个问题,您需要将模型层的属性设置为动画的最终值,然后然后将动画添加到层中。您希望按此顺序执行此操作,因为更改图层属性可以为该属性添加 隐式 动画,这会与您要显式添加的动画冲突。您要确保显式动画覆盖隐式动画。

那么你是如何做到这一切的呢?基本配方如下所示:

CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
animation.fromValue = [NSValue valueWithCGPoint:myLayer.position];
layer.position = newPosition; // HERE I UPDATE THE MODEL LAYER'S PROPERTY
animation.toValue = [NSValue valueWithCGPoint:myLayer.position];
animation.duration = .5;
[myLayer addAnimation:animation forKey:animation.keyPath];

我没有使用动画组,所以我不知道您可能需要更改什么。我只是将每个动画分别添加到图层中。

我还发现使用+[CATransaction setCompletionBlock:] 方法为一个或多个动画设置完成处理程序比尝试使用动画的委托更容易。您设置事务的完成块,然后添加动画:

[CATransaction begin]; 
    [CATransaction setCompletionBlock:^
        [self.imageView removeFromSuperview];
    ];
    [self addPositionAnimation];
    [self addScaleAnimation];
    [self addOpacityAnimation];
 [CATransaction commit];

【讨论】:

完美解决方案,我认为完成块仅适用于 UIView 动画方法。 这实际上是一个类别,所以我不得不使用关联对象来存储图像,并在动画完成后检索它以删除图像。使用这个块,我摆脱了所有的 hacky 代码 :) 对我来说,在添加动画之后更改模型层的属性是有效的。 几乎完美运行,只是模型层在调用完成块之前短暂出现。有什么办法可以避免吗? 但第一个解决方案的限制是,如果与“beginTime”一起使用,它不会正确显示,因为属性似乎在动画开始之前发生了变化。如果我们使用完成处理程序,可以在一秒钟内看到故障【参考方案2】:

CAAnimations 完成后会自动删除。有一个属性removedOnCompletion 可以控制它。您应该将其设置为NO

此外,还有一个称为fillMode 的东西,它控制动画在其持续时间之前和之后的行为。这是在CAMediaTiming 上声明的属性(CAAnimation 符合)。您应该将其设置为kCAFillModeForwards

通过这两项更改,动画在完成后应该会持续存在。但是,我不知道您是否需要在组中更改这些,或在组中的单个动画上,或两者兼而有之。

【讨论】:

这只是保持表示层可见。在动画按钮的情况下,您将无法在动画之后与按钮交互。 这实际上是持久化动画并强制它留在内存中的错误方式。最好的方法是创建动画,将其添加到图层,然后将您在图层上设置动画的属性设置为最终值。动画将覆盖动画持续时间和完成时的最终值您将看到处于最终状态的图层。不需要任何 fillMode 怪异。 动画对象不是特别大,所以我不关心“强制[ing]它留在内存中”。无论如何,只要动画完成,OP 就会删除图像视图,因此将动画保留在视图上的想法只是为了弥补动画完成和被删除视图之间的间隙。我确实同意更新视图的实际属性以匹配最终动画状态通常是您想要的,但是仍然存在持久化动画有用的情况,所以我会保持原样。 但是我发现“更新视图的实际属性以匹配最终动画状态”的问题:有时,模型层会在隐式动画开始之前先更新。我猜这是因为隐式动画是阻塞的——在后台线程中执行的隐式动画。有人遇到过同样的问题吗?【参考方案3】:

这是一个可以帮助某人的 Swift 示例

这是一个渐变层上的动画。它正在为.locations 属性设置动画。

@robMayoff 回答充分解释的关键点是:

令人惊讶的是,当您制作图层动画时,实际上是在开始动画之前设置了最终值!

以下是一个很好的例子,因为动画会无限重复。

当动画无休止地重复时,如果你犯了“在动画之前忘记设置值”的经典错误,你会偶尔看到动画之间的“闪光”!

var previousLocations: [NSNumber] = []
...

func flexTheColors()  // "flex" the color bands randomly
    
    let oldValues = previousTargetLocations
    let newValues = randomLocations()
    previousTargetLocations = newValues
    
    // IN FACT, ACTUALLY "SET THE VALUES, BEFORE ANIMATING!"
    theLayer.locations = newValues
    
    // AND NOW ANIMATE:
    CATransaction.begin()
    
    // and by the way, this is how you endlessly animate:
    CATransaction.setCompletionBlock [weak self] in
        if self == nil  return 
        self?.animeFlexColorsEndless()
    
    
    let a = CABasicAnimation(keyPath: "locations")
    a.isCumulative = false
    a.autoreverses = false
    a.isRemovedOnCompletion = true
    a.repeatCount = 0

    a.fromValue = oldValues
    a.toValue = newValues
    
    a.duration = (2.0...4.0).random()
    
    theLayer.add(a, forKey: nil)
    CATransaction.commit()

以下内容可能有助于为新程序员澄清一些事情。请注意,在我的代码中,我这样做:

    // IN FACT, ACTUALLY "SET THE VALUES, BEFORE ANIMATING!"
    theLayer.locations = newValues
    
    // AND NOW ANIMATE:
    CATransaction.begin()
    ...set up the animation...
    CATransaction.commit()

但是在另一个答案的代码示例中,它是这样的:

    CATransaction.begin()
    ...set up the animation...
    // IN FACT, ACTUALLY "SET THE VALUES, BEFORE ANIMATING!"
    theLayer.locations = newValues
    CATransaction.commit()

关于“在动画之前设置值!”的代码行的位置。 ..

实际上,将那行实际上“放在”begin-commit 代码行中是完全可以的。只要你在.commit()之前做。

我只提到这一点,因为它可能会使新动画师感到困惑。

【讨论】:

以上是关于Objective-C - CABasicAnimation 在动画后应用更改?的主要内容,如果未能解决你的问题,请参考以下文章

objective-c入门

Swift 3.0 令人兴奋,但Objective-C也有小改进--Objective-C的类属性

如何在c++中,调用objective-c

1了解Objective-C语言的起源

Objective-C 和 C++ 的区别有哪些?

Objective-C中的Hello World