如何同步动画UIView和CALayer

23

为了说明这个问题,我在Github上分享了一个非常小的Xcode项目(两个类,11KB下载大小)。点击这里查看代码片段或使用git clone git@gist.github.com:93982af3b65d2151672e.git


请考虑以下情景。我有一个自定义视图,名为'容器视图',其中包含两个小正方形。如下截图所示:

初始位置的两个正方形的屏幕截图

蓝色正方形是一个22x22pt的UIView实例,红色正方形是一个22x22pt的CALayer实例。对于这个问题,我想让这两个正方形“粘”在外部视图的右下角,同时我要动画显示该外部视图的框架。

我在UIView的类方法animateWithDuration:delay:options:animations:completion:中更改了容器视图的框架,使用了类型为UIViewAnimationCurveEaseOut的非默认缓动参数。

[UIView animateWithDuration:1.0 delay:0 
                    options:UIViewAnimationCurveEaseOut 
                 animations:^{ _containerView.frame = randomFrame; } 
                 completion:nil];
注意: 蓝色UIView和红色CALayer的frame都在容器视图的重写setFrame:方法中设置,但如果我将蓝色UIView的autoresizingMask属性设置为UIView的左侧和顶部边距自适应,结果也将是相同的。
在生成的动画中,蓝色正方形会像我期望的那样“粘”在角落上,但是红色正方形完全没有考虑动画的时间和缓动。我认为这是由于Core Animation的隐式动画特性造成的,这个特性在以前的许多情况下都对我有所帮助。
以下是一些动画序列中的截图,用于说明这两个正方形的非同步性: Three screenshots mid-animation where the red square is not in the 'right' place 问题来了:有没有一种方法可以同步两个frame的更改,使得红色CALayer和蓝色UIView都以相同的动画持续时间和缓动曲线移动,并像一个视图一样黏在一起? 附:当然,可以通过许多方式实现两个正方形黏在一起的所需视觉效果,例如让两个图层都变成CALayerUIView,但实际问题所在的项目有一个非常合法的原因,其中一个是CALayer,另一个是UIView。
2个回答

10

我假设你已经正确得到了结束位置,并且在动画后视图和图层是对齐的。这与动画无关,只涉及几何学。


当您更改CALayer的一个属性而不是一个视图的层(就像在您的情况下)时,它会隐式地动画到其新值。要自定义此动画,您可以使用显式动画,例如基本动画。在基本动画中更改帧看起来像这样:

CABasicAnimation *myAnimation = [CABasicAnimation animationWithKeyPath:@"frame"];
[myAnimation setToValue:[NSValue valueWithCGRect:myNewFrame]];
[myAnimation setFromValue:[NSValue valueWithCGRect:[myLayer frame]]];
[myAnimation setDuration:1.0];
[myAnimation setTimingFunction:[CAMediaTimingFuntion functionWithName: kCAMediaTimingFunctionEaseOut]];

[myLayer setFrame:myNewFrame];
[myLayer addAnimation:myAnimation forKey:@"someKeyForMyAnimation"];
如果两个动画的时序函数和持续时间相同,则它们应该保持对齐。
此外,请注意,显式动画不会改变值,您必须同时添加动画并设置新值(就像上面的示例一样)。
是的,有许多方法可以实现相同的效果。一个例子是将视图和图层作为同一个子视图的子视图(而这个子视图又是外部框架的子视图)。
编辑:你不能轻松地将UIView动画与显式动画分组。也不能使用动画组(因为它们应用于不同的图层),但是可以使用CATransaction。
[CATransaction begin];
[CATransaction setAnimationDuration:1.0];
[CATransaction setAnimationTimingFunction:[CAMediaTimingFuntion functionWithName: kCAMediaTimingFunctionEaseOut]];

// Layer animation
CABasicAnimation *myAnimation = [CABasicAnimation animationWithKeyPath:@"frame"];
[myAnimation setToValue:[NSValue valueWithCGRect:myNewFrame]];
[myAnimation setFromValue:[NSValue valueWithCGRect:[myLayer frame]]];

[myLayer setFrame:myNewFrame];
[myLayer addAnimation:myAnimation forKey:@"someKeyForMyAnimation"];

// Outer animation
CABasicAnimation *outerAnimation = [CABasicAnimation animationWithKeyPath:@"frame"];
[outerAnimation setToValue:[NSValue valueWithCGRect:myNewOuterFrame]];
[outerAnimation setFromValue:[NSValue valueWithCGRect:[outerView frame]]];

[[outerView layer] setFrame:myNewOuterFrame];
[[outerView layer] addAnimation:outerAnimation forKey:@"someKeyForMyOuterAnimation"];

[CATransaction commit];

你的顶级假设是正确的,正方形总是以应该的方式结束,但尽管使用CABasicAnimation确实会改变动画有些,但它远远不同步。我几乎怀疑Core Animation和UIView的两个缓动函数不同(而且您的CAMediaTimingFunction中有一个小错别字)。您可以在gist项目中尝试它。 - epologee
有没有一种方法可以同时更改UIView和CALayer的框架?底部的建议对于“真正的问题”无效,因为CALayer只会重新定位,而UIView也会调整大小。 - epologee
1
@DavidRönnqvist 今天你的回答已经第二次对我非常有帮助了...谢谢! - Lindemann

1
如果您想要 David Rönnqvist 在 Swift 3.0 中的代码片段,那么这里就有了:
    CATransaction.begin()
    CATransaction.setAnimationDuration(1.0)
    CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut))

    // Layer animation
    let myAnimation = CABasicAnimation(keyPath: "frame");
    myAnimation.toValue = NSValue(cgRect: myNewFrame)
    myAnimation.fromValue = NSValue(cgRect: myLayer.frame)

    myLayer.frame = myNewFrame
    myLayer.add(myAnimation, forKey: "someKeyForMyAnimation")

    // Outer animation
    let outerAnimation = CABasicAnimation(keyPath: "frame")
    outerAnimation.toValue = NSValue(cgRect: myNewOuterFrame)
    outerAnimation.fromValue = NSValue(cgRect: outerView.frame)

    outerView.layer.frame = myNewOuterFrame
    outerView.layer.add(outerAnimation, forKey: "someKeyForMyOuterAnimation")

    CATransaction.commit()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接