61

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];
4

3 回答 3

189

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

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

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

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

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];
于 2012-07-17T03:45:10.313 回答
32

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

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

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

于 2012-07-17T03:45:39.027 回答
2

这是一个可以帮助某人的 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().

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

于 2017-12-10T18:40:24.077 回答