如何在两个动画之间加入延迟并按顺序播放

7

我有一个UIImageView,我想要让它旋转180度,用时1秒钟,然后停留1秒钟,再用时1秒钟将其旋转180度回到原始位置。

我该如何实现这个目标?我已经尝试了100种方法,但它一直会弹回来,而不是旋转回去。

编辑:我忘记添加需要无限重复此操作。

2个回答

14

你只需要创建一个关键帧动画。 它被设计用于将多个动画链接在一起,在第一个关键帧中通过PI旋转你的UIImageView子类,在第二个关键帧中将其转换回identity

let rotateForwardAnimationDuration: TimeInterval = 1
let rotateBackAnimationDuration: TimeInterval = 1
let animationDuration: TimeInterval = rotateForwardAnimationDuration + rotateBackAnimationDuration
        
UIView.animateKeyframes(withDuration: animationDuration, delay: 0, options: [], animations: { 
    UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: rotateForwardAnimationDuration) {
        self.imageView.transform = CGAffineTransform(rotationAngle: .pi)
    }
            
    UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: rotateBackAnimationDuration) { 
        self.imageView.transform = .identity 
    }
})

结果:

enter image description here

编辑: 这是一个无限循环播放的示例。我假设您的图像在视图控制器中,并且保持对imageView的某些引用。

例如,当 viewDidAppear 被调用时,调用一个函数来触发动画,然后在动画完成的完成块中,再次调用相同的函数。

class ViewController: UIViewController {
    
    @IBOutlet weak var imageView: UIImageView!
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        self.runRotateAnimation()
    }
    
    func runRotateAnimation() {
        let rotateForwardAnimationDuration: TimeInterval = 1
        let rotateBackAnimationDuration: TimeInterval = 1
        let animationDuration: TimeInterval = rotateForwardAnimationDuration + rotateBackAnimationDuration
        
        UIView.animateKeyframes(withDuration: animationDuration, delay: 0, options: [], animations: { 
            UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: rotateForwardAnimationDuration) {
                self.imageView.transform = CGAffineTransform(rotationAngle: .pi)
            }
            
            UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: rotateBackAnimationDuration) { 
                self.imageView.transform = .identity 
            }
        }) { (isFinished) in
            // To loop it continuosly, just call the same function from the completion block of the keyframe animation
            self.runRotateAnimation()
        }
    }
}

谢谢您的回复!这个可以运行一次,如果我需要重复怎么办?我已经更新了我的问题。 - mikkelam
谢谢!似乎随着时间的推移,这会大大污染栈,这没有关系吗? - mikkelam
你说的“污染栈”是什么意思?每个递归函数都会污染栈,无论你做什么。如果你关心性能,那就看看CABasicAnimation,它是一个更轻量级的动画,支持repeatCount属性。 - dirtydanee
是的,每个递归函数都会填满堆栈,但这个函数会一直持续下去,通常人们不希望出现这种情况。如果您有多个这样的函数在短时间内运行,它可能会填满堆栈。我将尝试将其转换为循环。谢谢! - mikkelam
这样无限期地运行动画会导致性能问题,而这对结果没有影响。就像我提到的,你需要采用不同的方法。 - dirtydanee

9

您可以在UIView.animate的completionHandler中执行第二个动画。

let duration = self.transitionDuration(using: transitionContext)

let firstAnimDuration = 0.5
UIView.animate(withDuration: firstAnimDuration, animations: {
    /* Do here the first animation */
}) { (completed) in

   let secondAnimDuration = 0.5
   UIView.animate(withDuration: secondAnimDuration, animations: { 
       /* Do here the second animation */
   })
}

现在你可能会遇到另一个问题。

如果您使用CGAffineTransform旋转视图,并为每个动画分配此类型的新对象以应用于您的view.transform,则将丢失先前的变换操作。

因此,根据这篇文章:如何在Swift中应用多个变换,您需要连接变换操作。

具有2个动画块的示例

这是一个示例,将旋转180度并在1秒后返回原点:

let view = UIView.init(frame: CGRect.init(origin: self.view.center, size: CGSize.init(width: 100, height: 100)))
view.backgroundColor = UIColor.red
self.view.addSubview(view)

var transform = view.transform
transform = transform.rotated(by: 180)

UIView.animate(withDuration: 2, animations: {
    view.transform = transform
}) { (completed) in

    transform = CGAffineTransform.identity
    UIView.animate(withDuration: 2, delay: 1, options: [], animations: { 
        view.transform = transform
    }, completion: nil)
}

.repeat动画和.autoreverse的示例

.animate方法为您提供了设置一些动画选项的能力。特别是UIViewAnimationOptions结构包含:

  1. .repeat,无限重复您的动画块
  2. .autoreverse,将视图恢复到原始状态

有了这个想法,您可以这样做:

var transform = view.transform.rotated(by: 180)
UIView.animate(withDuration: 2, delay: 0, options: [.repeat, .autoreverse], animations: {
     self.myView.transform = transform
})

但是,您需要在两个动画之间有一个延迟,因此您需要使用以下技巧:

递归动画和1秒延迟的示例

只需在您的ViewController中创建一个方法来为您的视图进行动画处理。在最后一个completionHandler中,只需调用该方法来创建无限循环。

最后,您需要在viewDidAppear上调用该方法以启动动画。

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    self.animation()
}


func animation() {
    var transform = view.transform
    transform = transform.rotated(by: 180)

    UIView.animate(withDuration: 2, delay: 0, options: [], animations: {

        self.myView.transform = transform

    }) { bool in
        transform = CGAffineTransform.identity

        UIView.animate(withDuration: 2, delay: 1, options: [], animations: {

            self.myView.transform = transform

        }, completion: { bool in
            self.animation()
        })
    }
}

谢谢您的回复!这个可以运行一次,如果我需要重复怎么办?我已经更新了我的问题。 - mikkelam
2
很好,但是递归不会因为内存超载而出现问题吗?只是好奇。 - Neelam Verma

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