为空TableView添加自定义动画:替代GIF的实现方案咨询
嘿,这个问题我太有共鸣了——GIF在UIImageView里确实经常掉链子,尤其是需要高画质和流畅度的时候。给你几个比GIF靠谱得多的实现思路,亲测好用:
方案1:逐帧图片+UIView基础动画(最易上手,画质拉满)
把风滚草的滚动动作拆成多张高清PNG(比如每旋转45度一张,8帧左右就足够流畅),然后用UIImageView的逐帧动画配合位移动画,完全原生实现,没有第三方依赖,画质和流畅度都能和App其他元素保持一致。
示例代码(Swift):
// 先添加线条视图,确保在TableView为空时显示 func setupEmptyStateAnimation() { guard tableView.numberOfRows(inSection: 0) == 0 else { // 有内容时移除所有动画视图 tableView.subviews.filter { $0.tag == 999 }.forEach { $0.removeFromSuperview() } return } // 1. 添加线条并显示10-15秒 let lineImageView = UIImageView(image: UIImage(named: "empty_line")) lineImageView.tag = 999 lineImageView.frame = CGRect(x: 20, y: tableView.center.y, width: tableView.bounds.width - 40, height: 2) lineImageView.contentMode = .scaleToFill tableView.addSubview(lineImageView) // 2. 延迟12秒启动风滚草动画 DispatchQueue.main.asyncAfter(deadline: .now() + 12) { // 加载风滚草的逐帧图片 let tumbleweedFrames = (1...8).compactMap { UIImage(named: "tumbleweed_frame_\($0)") } guard !tumbleweedFrames.isEmpty else { return } let tumbleweedView = UIImageView() tumbleweedView.tag = 999 tumbleweedView.animationImages = tumbleweedFrames tumbleweedView.animationDuration = 2 // 滚完一圈的时间 tumbleweedView.animationRepeatCount = 1 // 只滚一次 // 初始位置在屏幕右侧外 tumbleweedView.frame = CGRect(x: tableView.bounds.width + 60, y: tableView.center.y - 30, width: 60, height: 60) self.tableView.addSubview(tumbleweedView) // 启动逐帧滚动 tumbleweedView.startAnimating() // 位移动画:从右滚到左 UIView.animate(withDuration: 3, delay: 0, options: .curveLinear) { tumbleweedView.frame.origin.x = -60 } completion: { _ in // 动画结束后清理视图 tumbleweedView.removeFromSuperview() // 可以选择隐藏线条,或者重新触发动画循环 lineImageView.isHidden = true } } }
方案2:Core Animation组合动画(更真实的物理效果)
如果想让风滚草的运动更贴近真实(比如滚动时轻微上下颠簸),可以用Core Animation的关键帧动画,把旋转、位移甚至缩放动画组合起来,自由度更高。
示例代码:
func setupAdvancedEmptyAnimation() { guard tableView.numberOfRows(inSection: 0) == 0 else { tableView.subviews.filter { $0.tag == 999 }.forEach { $0.removeFromSuperview() } return } // 添加线条 let lineImageView = UIImageView(image: UIImage(named: "empty_line")) lineImageView.tag = 999 lineImageView.frame = CGRect(x: 20, y: tableView.center.y, width: tableView.bounds.width - 40, height: 2) tableView.addSubview(lineImageView) DispatchQueue.main.asyncAfter(deadline: .now() + 10) { let tumbleweedView = UIImageView(image: UIImage(named: "tumbleweed_single")) tumbleweedView.tag = 999 tumbleweedView.frame = CGRect(x: self.tableView.bounds.width + 60, y: self.tableView.center.y - 30, width: 60, height: 60) self.tableView.addSubview(tumbleweedView) // 1. 位移动画:从右到左,加一点上下颠簸 let positionAnim = CAKeyframeAnimation(keyPath: "position") positionAnim.values = [ CGPoint(x: self.tableView.bounds.width + 60, y: self.tableView.center.y - 30), CGPoint(x: self.tableView.bounds.width/2, y: self.tableView.center.y - 40), CGPoint(x: 0, y: self.tableView.center.y - 25), CGPoint(x: -60, y: self.tableView.center.y - 30) ] positionAnim.duration = 3.5 positionAnim.timingFunction = CAMediaTimingFunction(name: .easeInOut) // 2. 旋转动画:同步滚动 let rotationAnim = CABasicAnimation(keyPath: "transform.rotation.z") rotationAnim.toValue = CGFloat.pi * 5 // 滚2.5圈 rotationAnim.duration = 3.5 rotationAnim.timingFunction = CAMediaTimingFunction(name: .linear) // 把动画加到图层 tumbleweedView.layer.add(positionAnim, forKey: "move") tumbleweedView.layer.add(rotationAnim, forKey: "rotate") // 动画结束后更新真实位置并清理 DispatchQueue.main.asyncAfter(deadline: .now() + 3.5) { tumbleweedView.frame.origin = CGPoint(x: -60, y: self.tableView.center.y - 30) tumbleweedView.removeFromSuperview() lineImageView.isHidden = true } } }
方案3:Lottie矢量动画(零代码动画制作)
如果不想自己拆帧或者写动画逻辑,可以用Lottie:用After Effects制作你想要的动画(线条显示→风滚草滚动),然后导出成Lottie的JSON文件,集成Lottie库后直接播放就行。优点是画质是矢量的,放大不会糊,动画细节可以做得非常精致,而且控制起来很灵活(比如暂停、循环、调整速度)。
示例代码:
import Lottie func setupLottieEmptyAnimation() { guard tableView.numberOfRows(inSection: 0) == 0 else { tableView.subviews.filter { $0 is LottieAnimationView }.forEach { $0.removeFromSuperview() } return } let animationView = LottieAnimationView(name: "empty_tumbleweed_animation") animationView.frame = tableView.bounds animationView.contentMode = .scaleAspectFit animationView.loopMode = .playOnce tableView.addSubview(animationView) // 播放动画,结束后移除 animationView.play { finished in if finished { animationView.removeFromSuperview() } } }
额外注意点
- 记得在TableView有内容时,及时移除所有动画视图,避免遮挡内容
- 图片资源要准备@2x、@3x版本,适配不同分辨率的设备
- 如果需要循环播放动画,可以在动画结束后重新调用setup方法
内容的提问来源于stack exchange,提问作者Kárpáti András
相关产品推荐
相关产品推荐

