【问题标题】:Staggered animations with CAKeyframeAnimation?使用 CAKeyframeAnimation 的交错动画?
【发布时间】:2013-10-19 21:34:44
【问题描述】:

我想在特定时间点为 3 个不同的图像制作动画,使其具有这种行为。

1) 1st image moves from (Xx, Yx) to (Xz,Yz) 
2) Wait 10 seconds
3) 2nd image appears in place at Xa,Yb
4) Wait half as long as in step 2
5) Fade out 2nd image
6) 3rd image appears at the same place as 2nd image

如果这些图像的每个动画都在它们自己的 CALayers 上,我可以将 CAKeyframeAnimation 与多个图层一起使用吗?如果没有,还有什么方法可以制作交错动画?

我正在尝试为扑克牌从屏幕外移动到特定位置设置动画,然后几秒钟后屏幕上会出现其他一些技巧。

【问题讨论】:

    标签: ios core-animation


    【解决方案1】:

    已编辑

    当我写这篇文章时,我认为你不能使用 CAAnimationGroup 来为多个图层设置动画。 Matt 刚刚发布了一个答案,表明您可以做到这一点。我在此吃我的话。

    我已将 Matt 答案中的代码改编为我已上传到 Github (link.) 的项目

    Matt 的动画创建的效果是一双脚在屏幕上行走。找了一些开源的foot,安装在项目中,做了一些改动,但是基本的做法是Matt的。给他的道具。

    效果如下:

    (以下说法不正确) 不,您不能使用关键帧动画来制作多个图层的动画。给定的 CAAnimation 只能作用于单个图层。顺便说一下,这包括图层组。

    如果您所做的只是在直线上移动图像、淡出和淡入,为什么不使用 UIView 动画呢?看看名字以 animateWithDuration:animations 开头的方法:它们可以让你同时创建多个动画,然后完成块可以触发更多的动画。

    如果您出于某种原因需要使用图层动画,可以使用 beginTime 属性(CAAnimation 对象具有该属性,因为它们符合 CAMediaTiming 协议。)对于不属于动画组的 CAAnimations,您可以使用

    animation.beginTime = CACurrentMediaTime() + delay;
    

    其中 delay 是一个以秒为单位的双精度数。

    如果延迟为 0,动画将开始。

    第三种选择是将您的视图控制器设置为动画的委托,并使用 animationDidStop:finished: 方法链接您的动画。在我看来,这最终是最混乱的实施方法。

    【讨论】:

    • 我想在发生旋转时停止动画,这就是为什么我没有查看 UIView 动画方法的原因。
    • 我可以使用具有多个层的 CAAnimationGroup,每层都有一个 animationGroup 吗?
    • 单个 CAAnimation 对象只能为一个图层设置动画。这包括它包含的任何 CAAnimationGroups。您可以提交同时运行的多个动画,为不同的图层设置动画。
    • “给定的 CAAnimation 只能作用于单个图层。顺便说一下,这包括组图层。”那是假的。单个 CAAnimationGroup,通过其子动画,可以引用并且可以为添加它的图层的多个子图层(在任何深度)设置动画。我并不是说提出的替代方案不是好的技术,但它们并没有像答案似乎暗示的那样强加给我们。只是说链接或延迟的动画“更容易”或其他东西可能会更好。 — (此外,现在存在关键帧 view 动画,并且显然可以为多个视图设置动画。)
    • @Matt,这是一篇很老的帖子,但根据我的经验,CAAnimationGroup 只能管理单层的动画。我很想被证明是错的。您是否有使用动画组来管理多个子层上的动画的示例? (我认为您将动画组附加到包含您正在制作动画的所有图层的父图层?)
    【解决方案2】:

    关于单个动画组不能为不同图层的属性设置动画的说法是不正确的。它可以。该技术是将动画组附加到超层,并在各个动画的关键路径中引用子层的属性。

    这是一个完整的示例,仅用于演示目的。启动时,该项目会显示两个“脚印”,它们交替进行,走出屏幕顶部。

    class ViewController: UIViewController, CAAnimationDelegate {
    
        let leftfoot = CALayer()
        let rightfoot = CALayer()
    
        override func viewDidLoad() {
            super.viewDidLoad()
            
            self.leftfoot.name = "left"
            self.leftfoot.contents = UIImage(named:"leftfoot")!.cgImage
            self.leftfoot.frame = CGRect(x: 100, y: 300, width: 50, height: 80)
            self.view.layer.addSublayer(self.leftfoot)
    
            self.rightfoot.name = "right"
            self.rightfoot.contents = UIImage(named:"rightfoot")!.cgImage
            self.rightfoot.frame = CGRect(x: 170, y: 300, width: 50, height: 80)
            self.view.layer.addSublayer(self.rightfoot)
    
            DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                self.start()
            }
        }
    
        func start() {
            let firstLeftStep = CABasicAnimation(keyPath: "sublayers.left.position.y")
            firstLeftStep.byValue = -80
            firstLeftStep.duration = 1
            firstLeftStep.fillMode = .forwards
    
            func rightStepAfter(_ t: Double) -> CABasicAnimation {
                let rightStep = CABasicAnimation(keyPath: "sublayers.right.position.y")
                rightStep.byValue = -160
                rightStep.beginTime = t
                rightStep.duration = 2
                rightStep.fillMode = .forwards
                return rightStep
            }
            func leftStepAfter(_ t: Double) -> CABasicAnimation {
                let leftStep = CABasicAnimation(keyPath: "sublayers.left.position.y")
                leftStep.byValue = -160
                leftStep.beginTime = t
                leftStep.duration = 2
                leftStep.fillMode = .forwards
                return leftStep
            }
    
            let group = CAAnimationGroup()
            group.duration = 11
            group.animations = [firstLeftStep]
            for i in stride(from: 1, through: 9, by: 4) {
                group.animations?.append(rightStepAfter(Double(i)))
                group.animations?.append(leftStepAfter(Double(i+2)))
            }
            group.delegate = self
            self.view.layer.add(group, forKey: nil)
        }
        
        func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
            print("done")
            self.rightfoot.removeFromSuperlayer()
            self.leftfoot.removeFromSuperlayer()
        }
    
    }
    

    说了这么多,我应该补充一点,如果您要为某个核心属性(如某物的位置)设置动画,将其设为视图并使用 UIView 关键帧动画来协调不同视图上的动画可能会更简单。不过,关键是说这个不能用 CAAnimationGroup 完成是错误的。

    【讨论】:

    • 非常酷。 (赞成。)我将不得不玩它。你有一个像 github 这样的 repo 上的示例项目可以分享吗?
    • 只要使用任意两张图片,复制粘贴我给出的代码即可。开箱即用。
    • 我冒昧地根据您的代码创建了一个 Github 项目。它工作得很好。您可以在以下链接下载它:github.com/DuncanMC/AnimationGroups/tree/master/AnimationGroups
    • 我修改了代码,根据imageHeight和imageWidth变量计算图层位置和动画步长,然后根据当前屏幕高度计算可以走的步数。
    • 请注意,我还将完成处理程序闭包附加到我的动画中。然后我设置我的视图控制器来寻找那个闭包,如果找到了,就运行它。这使得拥有非常灵活的动画完成方法成为可能。
    猜你喜欢
    • 2015-04-24
    • 2013-04-02
    • 1970-01-01
    • 2016-12-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-25
    • 1970-01-01
    相关资源
    最近更新 更多