Swift中如何实现带easeIn启动和easeOut停止的无限旋转动画?
实现带缓入缓出效果的轮盘旋转动画
你需要的是**启动时加速(easeIn)、中途匀速、停止时减速(easeOut)**的完整旋转效果,递归调用线性UIView动画的方式无法灵活控制这三个阶段的过渡,推荐用「CADisplayLink+分段UIView动画」的方案来实现:
核心思路
- 启动阶段:用easeIn动画让轮盘从静止加速到匀速状态
- 匀速阶段:通过CADisplayLink(和屏幕帧率同步的定时器)持续更新旋转角度,保证匀速转动的流畅性
- 停止阶段:先终止CADisplayLink,再用easeOut动画让轮盘从匀速减速到静止
完整代码实现
首先定义必要的属性:
import UIKit class RouletteViewController: UIViewController { @IBOutlet weak var imgViewRoulette: UIImageView! // 驱动匀速旋转的显示链接(和屏幕帧率同步) private var displayLink: CADisplayLink? // 累计旋转的总弧度(避免直接叠加transform导致精度丢失) private var totalRotation: CGFloat = 0.0 // 标记是否处于匀速旋转状态 private var isUniformRotating = false // 匀速角速度:每秒旋转4π弧度(即每秒2圈,可根据需求调整) private let uniformAngularSpeed: CGFloat = .pi * 4
然后实现启动旋转的方法:
// 启动轮盘旋转 func startRouletteSpin() { guard !isUniformRotating else { return } // 1. 执行缓入加速动画(0.5秒内加速到匀速) let accelerationDuration: TimeInterval = 0.5 let acceleratedRotation = uniformAngularSpeed * CGFloat(accelerationDuration) UIView.animate(withDuration: accelerationDuration, delay: 0, options: .curveEaseIn) { self.totalRotation += acceleratedRotation self.imgViewRoulette.transform = CGAffineTransform(rotationAngle: self.totalRotation) } completion: { [weak self] _ in guard let self = self else { return } // 2. 加速完成后,启动匀速旋转 self.startUniformRotation() } } // 启动匀速旋转逻辑 private func startUniformRotation() { isUniformRotating = true displayLink = CADisplayLink(target: self, selector: #selector(updateUniformRotation)) displayLink?.add(to: .current, forMode: .common) } // 每帧更新匀速旋转角度 @objc private func updateUniformRotation() { guard let link = displayLink else { return } // 根据帧率计算本次旋转的弧度增量 let rotationDelta = uniformAngularSpeed * CGFloat(link.duration) totalRotation += rotationDelta imgViewRoulette.transform = CGAffineTransform(rotationAngle: totalRotation) }
最后实现停止旋转的方法:
// 停止轮盘旋转 func stopRouletteSpin() { guard isUniformRotating else { return } // 1. 终止匀速旋转的displayLink displayLink?.invalidate() displayLink = nil isUniformRotating = false // 2. 执行缓出减速动画(0.5秒内从匀速减速到静止) let decelerationDuration: TimeInterval = 0.5 let deceleratedRotation = uniformAngularSpeed * CGFloat(decelerationDuration) UIView.animate(withDuration: decelerationDuration, delay: 0, options: .curveEaseOut) { self.totalRotation += deceleratedRotation self.imgViewRoulette.transform = CGAffineTransform(rotationAngle: self.totalRotation) } completion: { _ in // 动画完成,轮盘完全停止 } } }
关键细节说明
- 用
totalRotation累计旋转角度,避免直接叠加transform.rotated(by:)导致的精度丢失问题 - CADisplayLink和屏幕刷新率同步,比递归UIView动画的匀速效果更流畅,不会出现动画间隙
- 缓入/缓出的时长、匀速角速度都可以根据需求调整,适配不同的轮盘大小和交互场景
内容的提问来源于stack exchange,提问作者Gargo
相关产品推荐
相关产品推荐

