如何实现shimmer动画从一个UIView流向另一个UIView的连续循环效果
实现方案
1. 先优化ShimmerView基础实现
首先修复原实现重复添加梯度层的问题,同时支持单次动画结束回调,方便串联下一段动画:
class ShimmerView: UIView { private let gradientColorOne: CGColor = UIColor.purple.cgColor private let gradientColorTwo: CGColor = UIColor.yellow.cgColor // 把梯度层设为成员变量,避免重复添加 private var gradientLayer: CAGradientLayer? private func addGradientLayer() -> CAGradientLayer { // 已添加过就直接返回,避免重复创建 if let existLayer = gradientLayer { return existLayer } let gradientLayer = CAGradientLayer() gradientLayer.frame = self.bounds gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.0) gradientLayer.endPoint = CGPoint(x: 0.0, y: 1.0) gradientLayer.colors = [gradientColorOne, gradientColorTwo, gradientColorOne] gradientLayer.locations = [0.0, 0.5, 1.0] self.layer.addSublayer(gradientLayer) self.gradientLayer = gradientLayer return gradientLayer } private func addAnimation() -> CABasicAnimation { let animation = CABasicAnimation(keyPath: "locations") animation.fromValue = [-1.0, -0.5, 0.0] animation.toValue = [1.0, 1.5, 2.0] // 不要设置无限重复,改成单次动画 animation.repeatCount = 1 animation.duration = 2 // 动画结束后保持最终状态,避免闪回 animation.fillMode = .forwards animation.isRemovedOnCompletion = false return animation } // 新增带完成回调的启动方法 func startAnimating(completion: @escaping () -> Void) { let gradientLayer = addGradientLayer() let animation = addAnimation() CATransaction.begin() CATransaction.setCompletionBlock { [weak self] in // 动画结束后重置梯度层状态,为下一次播放做准备 self?.gradientLayer?.removeAllAnimations() self?.gradientLayer?.locations = [0.0, 0.5, 1.0] completion() } gradientLayer.add(animation, forKey: animation.keyPath) CATransaction.commit() } // 新增停止方法,页面退出时调用避免内存泄漏 func stopAnimating() { gradientLayer?.removeAllAnimations() gradientLayer?.removeFromSuperlayer() gradientLayer = nil } }
2. 实现视图间的动画序列循环
在父视图控制器中编写序列触发逻辑,通过动画结束回调串联两个视图的播放顺序,自动循环:
override func viewDidLoad() { super.viewDidLoad() view1.backgroundColor = .purple view2.backgroundColor = .purple // 启动循环序列 startShimmerSequence() } // 动画序列控制方法 private func startShimmerSequence() { // 先播放view1的动画 view1.startAnimating { [weak self] in guard let self = self else { return } // view1播放结束后播放view2的动画 self.view2.startAnimating { [weak self] in guard let self = self else { return } // view2播放结束后重新触发序列,实现无限循环 self.startShimmerSequence() } } } // 页面消失时停止动画,避免内存泄漏 override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) view1.stopAnimating() view2.stopAnimating() }
方案原理
原固定延迟方案的问题是动画执行的时间存在系统级的微小偏差,多次循环后偏差会累积导致时序错位。改用动画完成回调触发下一段动画的方式,每一轮的播放完全依赖上一段的结束信号,不会出现累积误差,能保证动画序列永远严格按照view1播放→view2播放→view1播放的逻辑循环。
内容的提问来源于stack exchange,提问作者Rumin
相关产品推荐
相关产品推荐

