如何在 iOS Swift 中制作加载动画?
Posted
技术标签:
【中文标题】如何在 iOS Swift 中制作加载动画?【英文标题】:How to make loading animation in iOS Swift? 【发布时间】:2020-06-05 05:49:26 【问题描述】:嗨,我想用 3 UIView
制作动画。主要问题是我无法通过从layer
中删除动画来停止动画。
代码如下:
class HTAnimatedTypingView: UIView
@IBOutlet weak var view1: UIView!
@IBOutlet weak var view2: UIView!
@IBOutlet weak var view3: UIView!
func startAnimation()
UIView.animate(withDuration: 0.5, delay: 0, options: .repeat, animations:
self.view1.frame.origin.y = 0
, completion: nil)
UIView.animate(withDuration: 0.3, delay: 0.5, options: .repeat, animations:
self.view2.frame.origin.y = 0
, completion: nil)
UIView.animate(withDuration: 0.2, delay: 1.0, options: .repeat, animations:
self.view3.frame.origin.y = 0
, completion: nil)
func stopAnimations()
self.view1.layer.removeAllAnimations()
self.view2.layer.removeAllAnimations()
self.view3.layer.removeAllAnimations()
以上代码的输出:
预期动画:
如何使它与开始动画和停止动画功能一起使用?提前谢谢...
【问题讨论】:
【参考方案1】:由于您需要在每个动画序列之间添加一些暂停,我个人会使用 关键帧 来完成,因为它为您提供了一些灵活性:
class AnimationViewController: UIViewController
private let stackView: UIStackView =
$0.distribution = .fill
$0.axis = .horizontal
$0.alignment = .center
$0.spacing = 10
return $0
(UIStackView())
private let circleA = UIView()
private let circleB = UIView()
private let circleC = UIView()
private lazy var circles = [circleA, circleB, circleC]
func animate()
let jumpDuration: Double = 0.30
let delayDuration: Double = 1.25
let totalDuration: Double = delayDuration + jumpDuration*2
let jumpRelativeDuration: Double = jumpDuration / totalDuration
let jumpRelativeTime: Double = delayDuration / totalDuration
let fallRelativeTime: Double = (delayDuration + jumpDuration) / totalDuration
for (index, circle) in circles.enumerated()
let delay = jumpDuration*2 * TimeInterval(index) / TimeInterval(circles.count)
UIView.animateKeyframes(withDuration: totalDuration, delay: delay, options: [.repeat], animations:
UIView.addKeyframe(withRelativeStartTime: jumpRelativeTime, relativeDuration: jumpRelativeDuration)
circle.frame.origin.y -= 30
UIView.addKeyframe(withRelativeStartTime: fallRelativeTime, relativeDuration: jumpRelativeDuration)
circle.frame.origin.y += 30
)
override func viewDidLoad()
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
circles.forEach
$0.layer.cornerRadius = 20/2
$0.layer.masksToBounds = true
$0.backgroundColor = .systemBlue
stackView.addArrangedSubview($0)
$0.widthAnchor.constraint(equalToConstant: 20).isActive = true
$0.heightAnchor.constraint(equalTo: $0.widthAnchor).isActive = true
override func viewDidAppear(_ animated: Bool)
super.viewDidAppear(animated)
animate()
应该很简单,但如果您有任何问题,请随时告诉我!
结果如下所示:
【讨论】:
【参考方案2】:一种方法是使用Timer
。在您的班级中保留Timer
的实例。当startAnimation
被调用时,安排它。当stopAnimation
被调用时,invalidate
它。 (这意味着当前正在进行的动画将在动画实际停止之前完成,IMO 使它成为一个不错的非突然停止)。
在计时器的每个滴答声中,为点设置一次动画。请注意,您在每个点上应用的动画应该具有相同的持续时间,就像在预期的输出中一样,它们都以相同的速率反弹,只是在不同的时刻。
一些说明性代码:
// startAnimation
timer = Timer.scheduledTimer(withTimeInterval: timerInterval, repeats: true) _ in
self.animateDotsOnce()
// stopAnimation
timer.invalidate()
// animateDotsOnce
UIView.animate(withDuration: animationDuration, delay: 0, animations:
self.view1.frame.origin.y = animateHeight
, completion:
_ in
UIView.animate(withDuration: animationDuration)
self.view1.frame.origin.y = 0
)
// plus the other two views, with different delays...
我会让你为每个视图找到合适的animateHeight
、timerInterval
、animationDuration
和延迟。
【讨论】:
谢谢!,效果很好!;我将 view2、view3 的延迟设置为 0.2、0.4,计时器间隔为 0.5(动画持续时间)+延迟 = 1.1;这符合我的预期! 我绝对不推荐使用定时器来完成这样的任务,因为它可以通过专用 API 以更高效的方式轻松完成:UIView.animateKeyframes。 @AlexLinares 是的,你说得对...我不熟悉animateKeyframes
...【参考方案3】:
我建议使用CAKeyframeAnimation
而不是处理完成块和那个巫术。这是一个简单的例子:
for i in 0 ..< 3
let bubble = UIView(frame: CGRect(x: 20 + i * 20, y: 200, width: 10, height: 10))
bubble.backgroundColor = .red
bubble.layer.cornerRadius = 5
self.view.addSubview(bubble)
let animation = CAKeyframeAnimation()
animation.keyPath = "position.y"
animation.values = [0, 10, 0]
animation.keyTimes = [0, 0.5, 1]
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
animation.duration = 1
animation.isAdditive = true
animation.repeatCount = HUGE
animation.timeOffset = CACurrentMediaTime() + 0.2 * Double(i)
bubble.layer.add(animation, forKey: "anim")
当您想删除动画时,您只需使用bubble.layer.removeAnimation(forKey: "anim")
。您可能需要使用计时功能或values
和keyTimes
来获得您想要的精确运动。但是关键帧是制作特定动画的方法。
旁注:这个例子在viewDidLoad
中不起作用,因为视图还没有超级视图,所以动画不起作用。如果你在viewDidAppear
中测试它,它会起作用。
【讨论】:
【参考方案4】:UIView 动画与 CALayer 动画不同,最好不要混用。 在本地编写并经过测试。
import UIKit
import SnapKit
class HTAnimatedTypingView: UIView
private let view0 = UIView()
private let view1 = UIView()
private let view2 = UIView()
init()
super.init(frame: CGRect.zero)
makeUI()
override init(frame: CGRect)
super.init(frame: frame)
makeUI()
required init?(coder: NSCoder)
super.init(coder: coder)
makeUI()
private func makeUI()
backgroundColor = UIColor.white
view0.backgroundColor = UIColor.red
view1.backgroundColor = UIColor.blue
view2.backgroundColor = UIColor.yellow
addSubview(view0)
addSubview(view1)
addSubview(view2)
view0.snp.makeConstraints (make) in
make.centerY.equalTo(self.snp.centerY)
make.width.equalTo(10)
make.height.equalTo(10)
make.left.equalTo(self.snp.left)
view1.snp.makeConstraints (make) in
make.centerY.equalTo(self.snp.centerY)
make.width.equalTo(10)
make.height.equalTo(10)
make.centerX.equalTo(self.snp.centerX)
view2.snp.makeConstraints (make) in
make.centerY.equalTo(self.snp.centerY)
make.width.equalTo(10)
make.height.equalTo(10)
make.right.equalTo(self.snp.right)
public func startAnimation()
let duration:CFTimeInterval = 0.5
let animation_delay:CFTimeInterval = 0.1
assert(duration >= animation_delay * 5, "animation_delay should be way smaller than duration in order to make animation natural")
let translateAnimation = CABasicAnimation(keyPath: "position.y")
translateAnimation.duration = duration
translateAnimation.repeatCount = Float.infinity
translateAnimation.toValue = 0
translateAnimation.fillMode = CAMediaTimingFillMode.both
translateAnimation.isRemovedOnCompletion = false
translateAnimation.autoreverses = true
view0.layer.add(translateAnimation, forKey: "translation")
DispatchQueue.main.asyncAfter(deadline: .now() + animation_delay) [unowned self ] in
self.view1.layer.add(translateAnimation, forKey: "translation")
DispatchQueue.main.asyncAfter(deadline: .now() + animation_delay * 2) [unowned self ] in
self.view2.layer.add(translateAnimation, forKey: "translation")
public func stopAnimation()
self.view0.layer.removeAllAnimations()
self.view1.layer.removeAllAnimations()
self.view2.layer.removeAllAnimations()
【讨论】:
以上是关于如何在 iOS Swift 中制作加载动画?的主要内容,如果未能解决你的问题,请参考以下文章